/*-------------------------------------------------------------------------
 *
 * tablecmds.c
 *	  创建和更改表结构和设置的命令
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/commands/tablecmds.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/attmap.h"
#include "access/genam.h"
#include "access/heapam.h"
#include "access/heapam_xlog.h"
#include "access/multixact.h"
#include "access/reloptions.h"
#include "access/relscan.h"
#include "access/sysattr.h"
#include "access/tableam.h"
#include "access/toast_compression.h"
#include "access/xact.h"
#include "access/xlog.h"
#include "access/xloginsert.h"
#include "catalog/catalog.h"
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_attrdef.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_depend.h"
#include "catalog/pg_foreign_table.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_largeobject.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
#include "catalog/storage.h"
#include "catalog/storage_xlog.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/comment.h"
#include "commands/defrem.h"
#include "commands/event_trigger.h"
#include "commands/policy.h"
#include "commands/sequence.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
#include "commands/trigger.h"
#include "commands/typecmds.h"
#include "commands/user.h"
#include "executor/executor.h"
#include "foreign/fdwapi.h"
#include "foreign/foreign.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "nodes/parsenodes.h"
#include "optimizer/optimizer.h"
#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_oper.h"
#include "parser/parse_relation.h"
#include "parser/parse_type.h"
#include "parser/parse_utilcmd.h"
#include "parser/parser.h"
#include "partitioning/partbounds.h"
#include "partitioning/partdesc.h"
#include "pgstat.h"
#include "rewrite/rewriteDefine.h"
#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
#include "storage/lock.h"
#include "storage/predicate.h"
#include "storage/smgr.h"
#include "tcop/utility.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/partcache.h"
#include "utils/relcache.h"
#include "utils/ruleutils.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "utils/timestamp.h"
#include "utils/typcache.h"

/*
 * 提交时的操作列表
 */
typedef struct OnCommitItem
{
	Oid			relid;			/* 关系的 relid */
	OnCommitAction oncommit;	/* 在 xact 结束时要做的事情 */

	/*
	 * 如果这个条目是在当前事务中创建的，
	 * creating_subid 是创建子事务的 ID；如果是在之前的
	 * 事务中创建，creating_subid 是零。如果在当前
	 * 事务中删除，deleting_subid 是删除子事务的 ID；如果没有
	 * 删除请求挂起，deleting_subid 是零。
	 */
	SubTransactionId creating_subid;
	SubTransactionId deleting_subid;
} OnCommitItem;

static List *on_commits = NIL;


/*
 * ALTER TABLE 的状态信息
 *
 * ALTER TABLE 的待处理工作队列是一个 AlteredTableInfo
 * 结构的列表，针对该操作修改的每个表（被命名的表
 * 加上任何受影响的子表）。我们保存要应用于此表的
 * 子命令列表（可能经过解析转换步骤的修改）；
 * 这些列表将在第 2 阶段执行。如果需要第 3 阶段步骤，
 * 所需的信息存储在约束和新值列表中。
 *
 * 第 2 阶段分为多个 passes；子命令在
 * 按子命令类型确定的 pass 中执行。
 */

#define AT_PASS_UNSET			-1	/* UNSET 将导致错误 */
#define AT_PASS_DROP			0	/* DROP（所有变体） */
#define AT_PASS_ALTER_TYPE		1	/* ALTER COLUMN TYPE */
#define AT_PASS_OLD_INDEX		2	/* 重新添加现有索引 */
#define AT_PASS_OLD_CONSTR		3	/* 重新添加现有约束 */
/* 我们可以在这里支持 RENAME COLUMN 的 pass，但当前未使用 */
#define AT_PASS_ADD_COL			4	/* ADD COLUMN */
#define AT_PASS_ADD_CONSTR		5	/* 添加约束（初步检查） */
#define AT_PASS_COL_ATTRS		6	/* 设置列属性，例如 NOT NULL */
#define AT_PASS_ADD_INDEXCONSTR	7	/* 添加基于索引的约束 */
#define AT_PASS_ADD_INDEX		8	/* 添加索引 */
#define AT_PASS_ADD_OTHERCONSTR	9	/* 添加其他约束，默认值 */
#define AT_PASS_MISC			10	/* 其他内容 */
#define AT_NUM_PASSES			11

typedef struct AlteredTableInfo
{
	/* 在任何工作开始之前保存的信息： */
	Oid			relid;			/* 要处理的关系 */
	char		relkind;		/* 它的 relkind */
	TupleDesc	oldDesc;		/* 修改前的元组描述符 */

	/*
	 * 在第 2 阶段临时设置，通常设置为 NULL。
	 *
	 * ATRewriteCatalogs 在开始时设置此值，并在 ATExecCmd
	 * 返回控制时关闭。这可以被 ATExecCmd 子程序利用， 
	 * 在事务边界之间关闭/重新打开。
	 */
	Relation	rel;

	/* 第 1 阶段为第 2 阶段保存的信息： */
	List	   *subcmds[AT_NUM_PASSES]; /* AlterTableCmd 的列表 */
	/* 第 1/2 阶段为第 3 阶段保存的信息： */
	List	   *constraints;	/* 新约束的列表 */
	List	   *newvals;		/* 新列值的列表 */
	List	   *afterStmts;		/* 实用命令解析树的列表 */
	bool		verify_new_notnull; /* 如果我们应该重新检查 NOT NULL，则为 T */
	int			rewrite;		/* 被强制重写的理由（如果有） */
	Oid			newAccessMethod;	/* 新访问方法；0 表示没有变化 */
	Oid			newTableSpace;	/* 新表空间；0 表示没有变化 */
	bool		chgPersistence; /* 如果使用了 SET LOGGED/UNLOGGED 则为 T */
	char		newrelpersistence;	/* 如果上述为真 */
	Expr	   *partition_constraint;	/* 用于附加分区验证 */
	/* 如果由于其他附加/删除而验证默认值则为真 */
	bool		validate_default;
	/* 完成 ALTER TYPE 操作后重建的对象 */
	List	   *changedConstraintOids;	/* 要重建的约束的 OID */
	List	   *changedConstraintDefs;	/* 相同的字符串定义 */
	List	   *changedIndexOids;	/* 要重建的索引的 OID */
	List	   *changedIndexDefs;	/* 相同的字符串定义 */
	char	   *replicaIdentityIndex;	/* 作为 REPLICA IDENTITY 重置的索引 */
	char	   *clusterOnIndex; /* 用于 CLUSTER 的索引 */
	List	   *changedStatisticsOids;	/* 要重建的统计信息的 OID */
	List	   *changedStatisticsDefs;	/* 相同的字符串定义 */
} AlteredTableInfo;

/* 描述在第3阶段扫描中要检查的新约束的结构 */
/* 注意：新的 NOT NULL 约束在其他地方处理 */
typedef struct NewConstraint
{
	char	   *name;			/* 约束名称，或 NULL 如果没有 */
	ConstrType	contype;		/* CHECK 或 FOREIGN */
	Oid			refrelid;		/* PK rel，如果是 FOREIGN */
	Oid			refindid;		/* PK 的索引的 OID，如果是 FOREIGN */
	Oid			conid;			/* pg_constraint 条目的 OID，如果是 FOREIGN */
	Node	   *qual;			/* 检查表达式或 CONSTR_FOREIGN 约束 */
	ExprState  *qualstate;		/* 检查表达式的执行状态 */
} NewConstraint;

/*
 * 描述在第3阶段复制期间需要计算的一个新列值的结构
 * （这可以是一个具有非空默认值的新列，或者
 * 是我们正在更改类型的列）。没有这样的条目的列
 * 在 ATRewriteTable 期间只是从旧表复制过来。请注意，
 * expr 是对 *旧* 表值的表达式，除非 is_generated
 * 为真；否则它是对 *新* 元组的列的表达式。
 */
typedef struct NewColumnValue
{
	AttrNumber	attnum;			/* 哪一列 */
	Expr	   *expr;			/* 要计算的表达式 */
	ExprState  *exprstate;		/* 执行状态 */
	bool		is_generated;	/* 这是一个 GENERATED 表达式吗？ */
} NewColumnValue;

/*
 * RemoveRelations 的错误报告支持
 */
struct dropmsgstrings
{
	char		kind;
	int			nonexistent_code;
	const char *nonexistent_msg;
	const char *skipping_msg;
	const char *nota_msg;
	const char *drophint_msg;
};

static const struct dropmsgstrings dropmsgstringarray[] = {
	{RELKIND_RELATION,
		ERRCODE_UNDEFINED_TABLE,
		gettext_noop("table \"%s\" does not exist"),
		gettext_noop("table \"%s\" does not exist, skipping"),
		gettext_noop("\"%s\" is not a table"),
	gettext_noop("Use DROP TABLE to remove a table.")},
	{RELKIND_SEQUENCE,
		ERRCODE_UNDEFINED_TABLE,
		gettext_noop("sequence \"%s\" does not exist"),
		gettext_noop("sequence \"%s\" does not exist, skipping"),
		gettext_noop("\"%s\" is not a sequence"),
	gettext_noop("Use DROP SEQUENCE to remove a sequence.")},
	{RELKIND_VIEW,
		ERRCODE_UNDEFINED_TABLE,
		gettext_noop("view \"%s\" does not exist"),
		gettext_noop("view \"%s\" does not exist, skipping"),
		gettext_noop("\"%s\" is not a view"),
	gettext_noop("Use DROP VIEW to remove a view.")},
	{RELKIND_MATVIEW,
		ERRCODE_UNDEFINED_TABLE,
		gettext_noop("materialized view \"%s\" does not exist"),
		gettext_noop("materialized view \"%s\" does not exist, skipping"),
		gettext_noop("\"%s\" is not a materialized view"),
	gettext_noop("Use DROP MATERIALIZED VIEW to remove a materialized view.")},
	{RELKIND_INDEX,
		ERRCODE_UNDEFINED_OBJECT,
		gettext_noop("index \"%s\" does not exist"),
		gettext_noop("index \"%s\" does not exist, skipping"),
		gettext_noop("\"%s\" is not an index"),
	gettext_noop("Use DROP INDEX to remove an index.")},
	{RELKIND_COMPOSITE_TYPE,
		ERRCODE_UNDEFINED_OBJECT,
		gettext_noop("type \"%s\" does not exist"),
		gettext_noop("type \"%s\" does not exist, skipping"),
		gettext_noop("\"%s\" is not a type"),
	gettext_noop("Use DROP TYPE to remove a type.")},
	{RELKIND_FOREIGN_TABLE,
		ERRCODE_UNDEFINED_OBJECT,
		gettext_noop("foreign table \"%s\" does not exist"),
		gettext_noop("foreign table \"%s\" does not exist, skipping"),
		gettext_noop("\"%s\" is not a foreign table"),
	gettext_noop("Use DROP FOREIGN TABLE to remove a foreign table.")},
	{RELKIND_PARTITIONED_TABLE,
		ERRCODE_UNDEFINED_TABLE,
		gettext_noop("table \"%s\" does not exist"),
		gettext_noop("table \"%s\" does not exist, skipping"),
		gettext_noop("\"%s\" is not a table"),
	gettext_noop("Use DROP TABLE to remove a table.")},
	{RELKIND_PARTITIONED_INDEX,
		ERRCODE_UNDEFINED_OBJECT,
		gettext_noop("index \"%s\" does not exist"),
		gettext_noop("index \"%s\" does not exist, skipping"),
		gettext_noop("\"%s\" is not an index"),
	gettext_noop("Use DROP INDEX to remove an index.")},
	{'\0', 0, NULL, NULL, NULL, NULL}
};

/* RemoveRelations 和 RangeVarCallbackForDropRelation 之间的通信 */
struct DropRelationCallbackState
{
	/* 这些字段由 RemoveRelations 设置： */
	char		expected_relkind;
	LOCKMODE	heap_lockmode;
	/* 这些字段是跟踪哪些子锁被持有的状态： */
	Oid			heapOid;
	Oid			partParentOid;
	/* 这些字段由 RangeVarCallbackForDropRelation 传回： */
	char		actual_relkind;
	char		actual_relpersistence;
};

/* ATSimplePermissions 的 Alter table 目标类型标志 */
#define		ATT_TABLE				0x0001
#define		ATT_VIEW				0x0002
#define		ATT_MATVIEW				0x0004
#define		ATT_INDEX				0x0008
#define		ATT_COMPOSITE_TYPE		0x0010
#define		ATT_FOREIGN_TABLE		0x0020
#define		ATT_PARTITIONED_INDEX	0x0040
#define		ATT_SEQUENCE			0x0080

/*
 * ForeignTruncateInfo
 *
 * 与外部表截断相关的信息。这用于
 * 哈希表中的元素。它使用服务器 OID 作为查找键，
 * 并包括每个服务器涉及的所有外部表的列表。
 */
typedef struct ForeignTruncateInfo
{
	Oid			serverid;
	List	   *rels;
} ForeignTruncateInfo;

/* 在 addFkConstraint() 中部分或完整的 FK 创建 */
typedef enum addFkConstraintSides
{
	addFkReferencedSide,
	addFkReferencingSide,
	addFkBothSides,
} addFkConstraintSides;

/*
 * 当父分区表被删除时，预期分区表也将被删除。
 * 因此，对于分区，我们使用 AUTO 依赖。
 * 否则，对于常规继承，使用 NORMAL 依赖。
 */
#define child_dependency_type(child_is_partition)	\
	((child_is_partition) ? DEPENDENCY_AUTO : DEPENDENCY_NORMAL)

static void fc_truncate_check_rel(Oid fc_relid, Form_pg_class fc_reltuple);
static void fc_truncate_check_perms(Oid fc_relid, Form_pg_class fc_reltuple);
static void fc_truncate_check_activity(Relation fc_rel);
static void fc_RangeVarCallbackForTruncate(const RangeVar *fc_relation,
										Oid fc_relId, Oid fc_oldRelId, void *fc_arg);
static List *fc_MergeAttributes(List *fc_schema, List *fc_supers, char fc_relpersistence,
							 bool fc_is_partition, List **fc_supconstr);
static bool fc_MergeCheckConstraint(List *fc_constraints, char *fc_name, Node *fc_expr);
static void fc_MergeAttributesIntoExisting(Relation fc_child_rel, Relation fc_parent_rel);
static void fc_MergeConstraintsIntoExisting(Relation fc_child_rel, Relation fc_parent_rel);
static void fc_StoreCatalogInheritance(Oid fc_relationId, List *fc_supers,
									bool fc_child_is_partition);
static void fc_StoreCatalogInheritance1(Oid fc_relationId, Oid fc_parentOid,
									 int32 fc_seqNumber, Relation fc_inhRelation,
									 bool fc_child_is_partition);
static int	fc_findAttrByName(const char *fc_attributeName, List *fc_schema);
static void fc_AlterIndexNamespaces(Relation fc_classRel, Relation fc_rel,
								 Oid fc_oldNspOid, Oid fc_newNspOid, ObjectAddresses *fc_objsMoved);
static void fc_AlterSeqNamespaces(Relation fc_classRel, Relation fc_rel,
							   Oid fc_oldNspOid, Oid fc_newNspOid, ObjectAddresses *fc_objsMoved,
							   LOCKMODE fc_lockmode);
static ObjectAddress fc_ATExecAlterConstraint(Relation fc_rel, AlterTableCmd *fc_cmd,
										   bool fc_recurse, bool fc_recursing, LOCKMODE fc_lockmode);
static bool fc_ATExecAlterConstrRecurse(Constraint *fc_cmdcon, Relation fc_conrel, Relation fc_tgrel,
									 Relation fc_rel, HeapTuple fc_contuple, List **fc_otherrelids,
									 LOCKMODE fc_lockmode);
static ObjectAddress fc_ATExecValidateConstraint(List **fc_wqueue,
											  Relation fc_rel, char *fc_constrName,
											  bool fc_recurse, bool fc_recursing, LOCKMODE fc_lockmode);
static int	fc_transformColumnNameList(Oid fc_relId, List *fc_colList,
									int16 *fc_attnums, Oid *fc_atttypids);
static int	fc_transformFkeyGetPrimaryKey(Relation fc_pkrel, Oid *fc_indexOid,
									   List **fc_attnamelist,
									   int16 *fc_attnums, Oid *fc_atttypids,
									   Oid *fc_opclasses);
static Oid	fc_transformFkeyCheckAttrs(Relation fc_pkrel,
									int fc_numattrs, int16 *fc_attnums,
									Oid *fc_opclasses);
static void fc_checkFkeyPermissions(Relation fc_rel, int16 *fc_attnums, int fc_natts);
static CoercionPathType fc_findFkeyCast(Oid fc_targetTypeId, Oid fc_sourceTypeId,
									 Oid *fc_funcid);
static void fc_validateForeignKeyConstraint(char *fc_conname,
										 Relation fc_rel, Relation fc_pkrel,
										 Oid fc_pkindOid, Oid fc_constraintOid);
static void fc_CheckAlterTableIsSafe(Relation fc_rel);
static void fc_ATController(AlterTableStmt *fc_parsetree,
						 Relation fc_rel, List *fc_cmds, bool fc_recurse, LOCKMODE fc_lockmode,
						 AlterTableUtilityContext *fc_context);
static void fc_ATPrepCmd(List **fc_wqueue, Relation fc_rel, AlterTableCmd *fc_cmd,
					  bool fc_recurse, bool fc_recursing, LOCKMODE fc_lockmode,
					  AlterTableUtilityContext *fc_context);
static void fc_ATRewriteCatalogs(List **fc_wqueue, LOCKMODE fc_lockmode,
							  AlterTableUtilityContext *fc_context);
static void fc_ATExecCmd(List **fc_wqueue, AlteredTableInfo *fc_tab,
					  AlterTableCmd *fc_cmd, LOCKMODE fc_lockmode, int fc_cur_pass,
					  AlterTableUtilityContext *fc_context);
static AlterTableCmd *fc_ATParseTransformCmd(List **fc_wqueue, AlteredTableInfo *fc_tab,
										  Relation fc_rel, AlterTableCmd *fc_cmd,
										  bool fc_recurse, LOCKMODE fc_lockmode,
										  int fc_cur_pass,
										  AlterTableUtilityContext *fc_context);
static void fc_ATRewriteTables(AlterTableStmt *fc_parsetree,
							List **fc_wqueue, LOCKMODE fc_lockmode,
							AlterTableUtilityContext *fc_context);
static void fc_ATRewriteTable(AlteredTableInfo *fc_tab, Oid fc_OIDNewHeap, LOCKMODE fc_lockmode);
static AlteredTableInfo *fc_ATGetQueueEntry(List **fc_wqueue, Relation fc_rel);
static void fc_ATSimplePermissions(AlterTableType fc_cmdtype, Relation fc_rel, int fc_allowed_targets);
static void fc_ATSimpleRecursion(List **fc_wqueue, Relation fc_rel,
							  AlterTableCmd *fc_cmd, bool fc_recurse, LOCKMODE fc_lockmode,
							  AlterTableUtilityContext *fc_context);
static void fc_ATCheckPartitionsNotInUse(Relation fc_rel, LOCKMODE fc_lockmode);
static void fc_ATTypedTableRecursion(List **fc_wqueue, Relation fc_rel, AlterTableCmd *fc_cmd,
								  LOCKMODE fc_lockmode,
								  AlterTableUtilityContext *fc_context);
static List *fc_find_typed_table_dependencies(Oid fc_typeOid, const char *fc_typeName,
										   DropBehavior fc_behavior);
static void fc_ATPrepAddColumn(List **fc_wqueue, Relation fc_rel, bool fc_recurse, bool fc_recursing,
							bool fc_is_view, AlterTableCmd *fc_cmd, LOCKMODE fc_lockmode,
							AlterTableUtilityContext *fc_context);
static ObjectAddress fc_ATExecAddColumn(List **fc_wqueue, AlteredTableInfo *fc_tab,
									 Relation fc_rel, AlterTableCmd **fc_cmd,
									 bool fc_recurse, bool fc_recursing,
									 LOCKMODE fc_lockmode, int fc_cur_pass,
									 AlterTableUtilityContext *fc_context);
static bool fc_check_for_column_name_collision(Relation fc_rel, const char *fc_colname,
											bool fc_if_not_exists);
static void fc_add_column_datatype_dependency(Oid fc_relid, int32 fc_attnum, Oid fc_typid);
static void fc_add_column_collation_dependency(Oid fc_relid, int32 fc_attnum, Oid fc_collid);
static void fc_ATPrepDropNotNull(Relation fc_rel, bool fc_recurse, bool fc_recursing);
static ObjectAddress fc_ATExecDropNotNull(Relation fc_rel, const char *fc_colName, LOCKMODE fc_lockmode);
static void fc_ATPrepSetNotNull(List **fc_wqueue, Relation fc_rel,
							 AlterTableCmd *fc_cmd, bool fc_recurse, bool fc_recursing,
							 LOCKMODE fc_lockmode,
							 AlterTableUtilityContext *fc_context);
static ObjectAddress fc_ATExecSetNotNull(AlteredTableInfo *fc_tab, Relation fc_rel,
									  const char *fc_colName, LOCKMODE fc_lockmode);
static void fc_ATExecCheckNotNull(AlteredTableInfo *fc_tab, Relation fc_rel,
							   const char *fc_colName, LOCKMODE fc_lockmode);
static bool fc_NotNullImpliedByRelConstraints(Relation fc_rel, Form_pg_attribute fc_attr);
static bool fc_ConstraintImpliedByRelConstraint(Relation fc_scanrel,
											 List *fc_testConstraint, List *fc_provenConstraint);
static ObjectAddress fc_ATExecColumnDefault(Relation fc_rel, const char *fc_colName,
										 Node *fc_newDefault, LOCKMODE fc_lockmode);
static ObjectAddress fc_ATExecCookedColumnDefault(Relation fc_rel, AttrNumber fc_attnum,
											   Node *fc_newDefault);
static ObjectAddress fc_ATExecAddIdentity(Relation fc_rel, const char *fc_colName,
									   Node *fc_def, LOCKMODE fc_lockmode);
static ObjectAddress fc_ATExecSetIdentity(Relation fc_rel, const char *fc_colName,
									   Node *fc_def, LOCKMODE fc_lockmode);
static ObjectAddress fc_ATExecDropIdentity(Relation fc_rel, const char *fc_colName, bool fc_missing_ok, LOCKMODE fc_lockmode);
static void fc_ATPrepDropExpression(Relation fc_rel, AlterTableCmd *fc_cmd, bool fc_recurse, bool fc_recursing, LOCKMODE fc_lockmode);
static ObjectAddress fc_ATExecDropExpression(Relation fc_rel, const char *fc_colName, bool fc_missing_ok, LOCKMODE fc_lockmode);
static ObjectAddress fc_ATExecSetStatistics(Relation fc_rel, const char *fc_colName, int16 fc_colNum,
										 Node *fc_newValue, LOCKMODE fc_lockmode);
static ObjectAddress fc_ATExecSetOptions(Relation fc_rel, const char *fc_colName,
									  Node *fc_options, bool fc_isReset, LOCKMODE fc_lockmode);
static ObjectAddress fc_ATExecSetStorage(Relation fc_rel, const char *fc_colName,
									  Node *fc_newValue, LOCKMODE fc_lockmode);
static void fc_ATPrepDropColumn(List **fc_wqueue, Relation fc_rel, bool fc_recurse, bool fc_recursing,
							 AlterTableCmd *fc_cmd, LOCKMODE fc_lockmode,
							 AlterTableUtilityContext *fc_context);
static ObjectAddress fc_ATExecDropColumn(List **fc_wqueue, Relation fc_rel, const char *fc_colName,
									  DropBehavior fc_behavior,
									  bool fc_recurse, bool fc_recursing,
									  bool fc_missing_ok, LOCKMODE fc_lockmode,
									  ObjectAddresses *fc_addrs);
static ObjectAddress fc_ATExecAddIndex(AlteredTableInfo *fc_tab, Relation fc_rel,
									IndexStmt *fc_stmt, bool fc_is_rebuild, LOCKMODE fc_lockmode);
static ObjectAddress fc_ATExecAddStatistics(AlteredTableInfo *fc_tab, Relation fc_rel,
										 CreateStatsStmt *fc_stmt, bool fc_is_rebuild, LOCKMODE fc_lockmode);
static ObjectAddress fc_ATExecAddConstraint(List **fc_wqueue,
										 AlteredTableInfo *fc_tab, Relation fc_rel,
										 Constraint *fc_newConstraint, bool fc_recurse, bool fc_is_readd,
										 LOCKMODE fc_lockmode);
static char *fc_ChooseForeignKeyConstraintNameAddition(List *fc_colnames);
static ObjectAddress fc_ATExecAddIndexConstraint(AlteredTableInfo *fc_tab, Relation fc_rel,
											  IndexStmt *fc_stmt, LOCKMODE fc_lockmode);
static ObjectAddress fc_ATAddCheckConstraint(List **fc_wqueue,
										  AlteredTableInfo *fc_tab, Relation fc_rel,
										  Constraint *fc_constr,
										  bool fc_recurse, bool fc_recursing, bool fc_is_readd,
										  LOCKMODE fc_lockmode);
static ObjectAddress fc_ATAddForeignKeyConstraint(List **fc_wqueue, AlteredTableInfo *fc_tab,
											   Relation fc_rel, Constraint *fc_fkconstraint,
											   bool fc_recurse, bool fc_recursing,
											   LOCKMODE fc_lockmode);
static void fc_validateFkOnDeleteSetColumns(int fc_numfks, const int16 *fc_fkattnums,
										 int fc_numfksetcols, const int16 *fc_fksetcolsattnums,
										 List *fc_fksetcols);
static ObjectAddress fc_addFkConstraint(addFkConstraintSides fc_fkside,
									 char *fc_constraintname,
									 Constraint *fc_fkconstraint, Relation fc_rel,
									 Relation fc_pkrel, Oid fc_indexOid,
									 Oid fc_parentConstr,
									 int fc_numfks, int16 *fc_pkattnum, int16 *fc_fkattnum,
									 Oid *fc_pfeqoperators, Oid *fc_ppeqoperators,
									 Oid *fc_ffeqoperators, int fc_numfkdelsetcols,
									 int16 *fc_fkdelsetcols, bool fc_is_internal);
static void fc_addFkRecurseReferenced(Constraint *fc_fkconstraint,
								   Relation fc_rel, Relation fc_pkrel, Oid fc_indexOid, Oid fc_parentConstr,
								   int fc_numfks, int16 *fc_pkattnum, int16 *fc_fkattnum,
								   Oid *fc_pfeqoperators, Oid *fc_ppeqoperators, Oid *fc_ffeqoperators,
								   int fc_numfkdelsetcols, int16 *fc_fkdelsetcols,
								   bool fc_old_check_ok,
								   Oid fc_parentDelTrigger, Oid fc_parentUpdTrigger);
static void fc_addFkRecurseReferencing(List **fc_wqueue, Constraint *fc_fkconstraint,
									Relation fc_rel, Relation fc_pkrel, Oid fc_indexOid, Oid fc_parentConstr,
									int fc_numfks, int16 *fc_pkattnum, int16 *fc_fkattnum,
									Oid *fc_pfeqoperators, Oid *fc_ppeqoperators, Oid *fc_ffeqoperators,
									int fc_numfkdelsetcols, int16 *fc_fkdelsetcols,
									bool fc_old_check_ok, LOCKMODE fc_lockmode,
									Oid fc_parentInsTrigger, Oid fc_parentUpdTrigger);
static void fc_CloneForeignKeyConstraints(List **fc_wqueue, Relation fc_parentRel,
									   Relation fc_partitionRel);
static void fc_CloneFkReferenced(Relation fc_parentRel, Relation fc_partitionRel);
static void fc_CloneFkReferencing(List **fc_wqueue, Relation fc_parentRel,
							   Relation fc_partRel);
static void fc_createForeignKeyCheckTriggers(Oid fc_myRelOid, Oid fc_refRelOid,
										  Constraint *fc_fkconstraint, Oid fc_constraintOid,
										  Oid fc_indexOid,
										  Oid fc_parentInsTrigger, Oid fc_parentUpdTrigger,
										  Oid *fc_insertTrigOid, Oid *fc_updateTrigOid);
static void fc_createForeignKeyActionTriggers(Relation fc_rel, Oid fc_refRelOid,
										   Constraint *fc_fkconstraint, Oid fc_constraintOid,
										   Oid fc_indexOid,
										   Oid fc_parentDelTrigger, Oid fc_parentUpdTrigger,
										   Oid *fc_deleteTrigOid, Oid *fc_updateTrigOid);
static bool fc_tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fc_fk,
										 Oid fc_partRelid,
										 Oid fc_parentConstrOid, int fc_numfks,
										 AttrNumber *fc_mapped_conkey, AttrNumber *fc_confkey,
										 Oid *fc_conpfeqop,
										 Oid fc_parentInsTrigger,
										 Oid fc_parentUpdTrigger,
										 Relation fc_trigrel);
static void fc_GetForeignKeyActionTriggers(Relation fc_trigrel,
										Oid fc_conoid, Oid fc_confrelid, Oid fc_conrelid,
										Oid *fc_deleteTriggerOid,
										Oid *fc_updateTriggerOid);
static void fc_GetForeignKeyCheckTriggers(Relation fc_trigrel,
									   Oid fc_conoid, Oid fc_confrelid, Oid fc_conrelid,
									   Oid *fc_insertTriggerOid,
									   Oid *fc_updateTriggerOid);
static void fc_ATExecDropConstraint(Relation fc_rel, const char *fc_constrName,
								 DropBehavior fc_behavior,
								 bool fc_recurse, bool fc_recursing,
								 bool fc_missing_ok, LOCKMODE fc_lockmode);
static void fc_ATPrepAlterColumnType(List **fc_wqueue,
								  AlteredTableInfo *fc_tab, Relation fc_rel,
								  bool fc_recurse, bool fc_recursing,
								  AlterTableCmd *fc_cmd, LOCKMODE fc_lockmode,
								  AlterTableUtilityContext *fc_context);
static bool fc_ATColumnChangeRequiresRewrite(Node *fc_expr, AttrNumber fc_varattno);
static ObjectAddress fc_ATExecAlterColumnType(AlteredTableInfo *fc_tab, Relation fc_rel,
										   AlterTableCmd *fc_cmd, LOCKMODE fc_lockmode);
static void fc_RememberConstraintForRebuilding(Oid fc_conoid, AlteredTableInfo *fc_tab);
static void fc_RememberIndexForRebuilding(Oid fc_indoid, AlteredTableInfo *fc_tab);
static void fc_RememberStatisticsForRebuilding(Oid fc_indoid, AlteredTableInfo *fc_tab);
static void fc_ATPostAlterTypeCleanup(List **fc_wqueue, AlteredTableInfo *fc_tab,
								   LOCKMODE fc_lockmode);
static void fc_ATPostAlterTypeParse(Oid fc_oldId, Oid fc_oldRelId, Oid fc_refRelId,
								 char *fc_cmd, List **fc_wqueue, LOCKMODE fc_lockmode,
								 bool fc_rewrite);
static void fc_RebuildConstraintComment(AlteredTableInfo *fc_tab, int fc_pass,
									 Oid fc_objid, Relation fc_rel, List *fc_domname,
									 const char *fc_conname);
static void fc_TryReuseIndex(Oid fc_oldId, IndexStmt *fc_stmt);
static void fc_TryReuseForeignKey(Oid fc_oldId, Constraint *fc_con);
static ObjectAddress fc_ATExecAlterColumnGenericOptions(Relation fc_rel, const char *fc_colName,
													 List *fc_options, LOCKMODE fc_lockmode);
static void fc_change_owner_fix_column_acls(Oid fc_relationOid,
										 Oid fc_oldOwnerId, Oid fc_newOwnerId);
static void fc_change_owner_recurse_to_sequences(Oid fc_relationOid,
											  Oid fc_newOwnerId, LOCKMODE fc_lockmode);
static ObjectAddress fc_ATExecClusterOn(Relation fc_rel, const char *fc_indexName,
									 LOCKMODE fc_lockmode);
static void fc_ATExecDropCluster(Relation fc_rel, LOCKMODE fc_lockmode);
static void fc_ATPrepSetAccessMethod(AlteredTableInfo *fc_tab, Relation fc_rel, const char *fc_amname);
static bool fc_ATPrepChangePersistence(Relation fc_rel, bool fc_toLogged);
static void fc_ATPrepSetTableSpace(AlteredTableInfo *fc_tab, Relation fc_rel,
								const char *fc_tablespacename, LOCKMODE fc_lockmode);
static void fc_ATExecSetTableSpace(Oid fc_tableOid, Oid fc_newTableSpace, LOCKMODE fc_lockmode);
static void fc_ATExecSetTableSpaceNoStorage(Relation fc_rel, Oid fc_newTableSpace);
static void fc_ATExecSetRelOptions(Relation fc_rel, List *fc_defList,
								AlterTableType fc_operation,
								LOCKMODE fc_lockmode);
static void fc_ATExecEnableDisableTrigger(Relation fc_rel, const char *fc_trigname,
									   char fc_fires_when, bool fc_skip_system, bool fc_recurse,
									   LOCKMODE fc_lockmode);
static void fc_ATExecEnableDisableRule(Relation fc_rel, const char *fc_rulename,
									char fc_fires_when, LOCKMODE fc_lockmode);
static void fc_ATPrepAddInherit(Relation fc_child_rel);
static ObjectAddress fc_ATExecAddInherit(Relation fc_child_rel, RangeVar *fc_parent, LOCKMODE fc_lockmode);
static ObjectAddress fc_ATExecDropInherit(Relation fc_rel, RangeVar *fc_parent, LOCKMODE fc_lockmode);
static void fc_drop_parent_dependency(Oid fc_relid, Oid fc_refclassid, Oid fc_refobjid,
								   DependencyType fc_deptype);
static ObjectAddress fc_ATExecAddOf(Relation fc_rel, const TypeName *fc_ofTypename, LOCKMODE fc_lockmode);
static void fc_ATExecDropOf(Relation fc_rel, LOCKMODE fc_lockmode);
static void fc_ATExecReplicaIdentity(Relation fc_rel, ReplicaIdentityStmt *fc_stmt, LOCKMODE fc_lockmode);
static void fc_ATExecGenericOptions(Relation fc_rel, List *fc_options);
static void fc_ATExecSetRowSecurity(Relation fc_rel, bool fc_rls);
static void fc_ATExecForceNoForceRowSecurity(Relation fc_rel, bool fc_force_rls);
static ObjectAddress fc_ATExecSetCompression(AlteredTableInfo *fc_tab, Relation fc_rel,
										  const char *fc_column, Node *fc_newValue, LOCKMODE fc_lockmode);

static void fc_index_copy_data(Relation fc_rel, RelFileNode fc_newrnode);
static const char *fc_storage_name(char fc_c);

static void fc_RangeVarCallbackForDropRelation(const RangeVar *fc_rel, Oid fc_relOid,
											Oid fc_oldRelOid, void *fc_arg);
static void fc_RangeVarCallbackForAlterRelation(const RangeVar *fc_rv, Oid fc_relid,
											 Oid fc_oldrelid, void *fc_arg);
static PartitionSpec *fc_transformPartitionSpec(Relation fc_rel, PartitionSpec *fc_partspec, char *fc_strategy);
static void fc_ComputePartitionAttrs(ParseState *fc_pstate, Relation fc_rel, List *fc_partParams, AttrNumber *fc_partattrs,
								  List **fc_partexprs, Oid *fc_partopclass, Oid *fc_partcollation, char fc_strategy);
static void fc_CreateInheritance(Relation fc_child_rel, Relation fc_parent_rel);
static void fc_RemoveInheritance(Relation fc_child_rel, Relation fc_parent_rel,
							  bool fc_allow_detached);
static ObjectAddress fc_ATExecAttachPartition(List **fc_wqueue, Relation fc_rel,
										   PartitionCmd *fc_cmd,
										   AlterTableUtilityContext *fc_context);
static void fc_AttachPartitionEnsureIndexes(Relation fc_rel, Relation fc_attachrel);
static void fc_QueuePartitionConstraintValidation(List **fc_wqueue, Relation fc_scanrel,
											   List *fc_partConstraint,
											   bool fc_validate_default);
static void fc_CloneRowTriggersToPartition(Relation fc_parent, Relation fc_partition);
static void fc_DetachAddConstraintIfNeeded(List **fc_wqueue, Relation fc_partRel);
static void fc_DropClonedTriggersFromPartition(Oid fc_partitionId);
static ObjectAddress fc_ATExecDetachPartition(List **fc_wqueue, AlteredTableInfo *fc_tab,
										   Relation fc_rel, RangeVar *fc_name,
										   bool fc_concurrent);
static void fc_DetachPartitionFinalize(Relation fc_rel, Relation fc_partRel,
									bool fc_concurrent, Oid fc_defaultPartOid);
static ObjectAddress fc_ATExecDetachPartitionFinalize(Relation fc_rel, RangeVar *fc_name);
static ObjectAddress fc_ATExecAttachPartitionIdx(List **fc_wqueue, Relation fc_parentIdx,
											  RangeVar *fc_name);
static void fc_validatePartitionedIndex(Relation fc_partedIdx, Relation fc_partedTbl);
static void fc_refuseDupeIndexAttach(Relation fc_parentIdx, Relation fc_partIdx,
								  Relation fc_partitionTbl);
static List *fc_GetParentedForeignKeyRefs(Relation fc_partition);
static void fc_ATDetachCheckNoForeignKeyRefs(Relation fc_partition);
static char fc_GetAttributeCompression(Oid fc_atttypid, char *fc_compression);


/* ----------------------------------------------------------------
 *		DefineRelation
 *				创建一个新的关系。
 *
 * stmt 携带来自普通 CREATE TABLE 语句的解析树信息。
 * 其他参数用于扩展其他案例的行为：
 * relkind：分配给新关系的 relkind
 * ownerId：如果不是 InvalidOid，则将其用作新关系的所有者。
 * typaddress：如果不为 null，则设置为 pg_type 条目的地址。
 * queryString：用于错误报告
 *
 * 请注意，权限检查是针对当前用户进行的，而不管
 * ownerId。非零 ownerId 用于当某人 "代表"
 * 其他人创建关系时，因此我们仍然希望看到当前用户
 * 有权限执行此操作。
 *
 * 如果成功，返回新关系的地址。
 * ----------------------------------------------------------------
 */
ObjectAddress DefineRelation(CreateStmt *fc_stmt, char fc_relkind, Oid fc_ownerId,
			   ObjectAddress *fc_typaddress, const char *fc_queryString)
{
	char		fc_relname[NAMEDATALEN];
	Oid			fc_namespaceId;
	Oid			fc_relationId;
	Oid			fc_tablespaceId;
	Relation	fc_rel;
	TupleDesc	fc_descriptor;
	List	   *fc_inheritOids;
	List	   *fc_old_constraints;
	List	   *fc_rawDefaults;
	List	   *fc_cookedDefaults;
	Datum		fc_reloptions;
	ListCell   *fc_listptr;
	AttrNumber	fc_attnum;
	bool		fc_partitioned;
	static char *fc_validnsps[] = HEAP_RELOPT_NAMESPACES;
	Oid			fc_ofTypeId;
	ObjectAddress fc_address;
	LOCKMODE	fc_parentLockmode;
	const char *fc_accessMethod = NULL;
	Oid			fc_accessMethodId = InvalidOid;

	/*
	 * 将 relname 截断到适当的长度（可能是浪费时间，因为
	 * 解析器应该已经完成了这项工作）。
	 */
	strlcpy(fc_relname, fc_stmt->relation->relname, NAMEDATALEN);

	/*
	 * 检查参数的一致性
	 */
	if (fc_stmt->oncommit != ONCOMMIT_NOOP
		&& fc_stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
				 errmsg("ON COMMIT can only be used on temporary tables")));

	if (fc_stmt->partspec != NULL)
	{
		if (fc_relkind != RELKIND_RELATION)
			elog(ERROR, "unexpected relkind: %d", (int) fc_relkind);

		fc_relkind = RELKIND_PARTITIONED_TABLE;
		fc_partitioned = true;
	}
	else
		fc_partitioned = false;

	/*
	 * 查找我们应该创建关系的命名空间，检查我们是否有权限在此处创建，锁定以防并发删除，
	 * 如果选择了临时命名空间，则将 stmt->relation 标记为 RELPERSISTENCE_TEMP。
	 */
	fc_namespaceId =
		RangeVarGetAndCheckCreationNamespace(fc_stmt->relation, NoLock, NULL);

	/*
	 * 安全检查：禁止从安全受限代码中创建临时表。
	 * 这一步骤是必要的，因为调用代码可能不希望在其搜索路径前面出现不可信的表
	 * pg_temp。
	 */
	if (fc_stmt->relation->relpersistence == RELPERSISTENCE_TEMP
		&& InSecurityRestrictedOperation())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("cannot create temporary table within security-restricted operation")));

	/*
	 * 确定用于扫描父项的锁模式。这里需要自排他锁。
	 *
	 * 对于常规继承，如果两个后端尝试同时向同一父项添加子项，而该父项没有现有子项，
	 * 那么这两个后端将尝试更新父项的 relhassubclass 字段，从而导致“元组并发更新”错误。
	 * 此外，这也防止了对父表的并发 ANALYZE，该操作可能会试图清除父项的 relhassubclass
	 * 字段，如果其之前的子项最近被删除。
	 *
	 * 如果子表是分区表，则我们改为在父项上获取独占锁，因为其分区描述符将因
	 * 新分区的添加而发生变化。
	 */
	fc_parentLockmode = (fc_stmt->partbound != NULL ? AccessExclusiveLock :
					  ShareUpdateExclusiveLock);

	/* 确定父项的 OID 列表。 */
	fc_inheritOids = NIL;
	foreach(fc_listptr, fc_stmt->inhRelations)
	{
		RangeVar   *fc_rv = (RangeVar *) lfirst(fc_listptr);
		Oid			fc_parentOid;

		fc_parentOid = RangeVarGetRelid(fc_rv, fc_parentLockmode, false);

		/*
		 * 拒绝父项列表中的重复项。
		 */
		if (list_member_oid(fc_inheritOids, fc_parentOid))
			ereport(ERROR,
					(errcode(ERRCODE_DUPLICATE_TABLE),
					 errmsg("relation \"%s\" would be inherited from more than once",
							get_rel_name(fc_parentOid))));

		fc_inheritOids = lappend_oid(fc_inheritOids, fc_parentOid);
	}

	/*
	 * 选择要使用的表空间：明确指定的一个，或者（在分区表的情况下）
	 * 父项的，如果它有的话。
	 */
	if (fc_stmt->tablespacename)
	{
		fc_tablespaceId = get_tablespace_oid(fc_stmt->tablespacename, false);

		if (fc_partitioned && fc_tablespaceId == MyDatabaseTableSpace)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("cannot specify default tablespace for partitioned relations")));
	}
	else if (fc_stmt->partbound)
	{
		Assert(list_length(fc_inheritOids) == 1);
		fc_tablespaceId = get_rel_tablespace(linitial_oid(fc_inheritOids));
	}
	else
		fc_tablespaceId = InvalidOid;

	/* 还是没有？使用默认值 */
	if (!OidIsValid(fc_tablespaceId))
		fc_tablespaceId = GetDefaultTablespace(fc_stmt->relation->relpersistence,
											fc_partitioned);

	/* 检查权限，除非使用数据库的默认值 */
	if (OidIsValid(fc_tablespaceId) && fc_tablespaceId != MyDatabaseTableSpace)
	{
		AclResult	fc_aclresult;

		fc_aclresult = pg_tablespace_aclcheck(fc_tablespaceId, GetUserId(),
										   ACL_CREATE);
		if (fc_aclresult != ACLCHECK_OK)
			aclcheck_error(fc_aclresult, OBJECT_TABLESPACE,
						   get_tablespace_name(fc_tablespaceId));
	}

	/* 在所有情况下，禁止将用户关系放在 pg_global 中 */
	if (fc_tablespaceId == GLOBALTABLESPACE_OID)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("only shared relations can be placed in pg_global tablespace")));

	/* 确定将拥有表的用户 ID */
	if (!OidIsValid(fc_ownerId))
		fc_ownerId = GetUserId();

	/*
	 * 解析和验证 reloptions（如果有）。
	 */
	fc_reloptions = transformRelOptions((Datum) 0, fc_stmt->options, NULL, fc_validnsps,
									 true, false);

	switch (fc_relkind)
	{
		case RELKIND_VIEW:
			(void) view_reloptions(fc_reloptions, true);
			break;
		case RELKIND_PARTITIONED_TABLE:
			(void) partitioned_table_reloptions(fc_reloptions, true);
			break;
		default:
			(void) heap_reloptions(fc_relkind, fc_reloptions, true);
	}

	if (fc_stmt->ofTypename)
	{
		AclResult	fc_aclresult;

		fc_ofTypeId = typenameTypeId(NULL, fc_stmt->ofTypename);

		fc_aclresult = pg_type_aclcheck(fc_ofTypeId, GetUserId(), ACL_USAGE);
		if (fc_aclresult != ACLCHECK_OK)
			aclcheck_error_type(fc_aclresult, fc_ofTypeId);
	}
	else
		fc_ofTypeId = InvalidOid;

	/*
	 * 查找继承祖先并生成关系模式，包括继承的属性。
	 * （注意，stmt->tableElts 是被 MergeAttributes destructively 修改的。）
	 */
	fc_stmt->tableElts =
		fc_MergeAttributes(fc_stmt->tableElts, fc_inheritOids,
						fc_stmt->relation->relpersistence,
						fc_stmt->partbound != NULL,
						&fc_old_constraints);

	/*
	 * 从关系模式创建元组描述符。注意，这处理列名、类型和 NOT NULL 约束，但不处理
	 * 默认值或 CHECK 约束；这些将在下面处理。
	 */
	fc_descriptor = BuildDescForRelation(fc_stmt->tableElts);

	/*
	 * 查找具有默认值的列，并准备插入默认值。
	 * 预先处理（即继承的）默认值将进入 CookedConstraint 结构的列表，我们将传递给 
	 * heap_create_with_catalog，而原始默认值将进入 RawColumnDefault 结构的列表，
	 * 将通过 AddRelationNewConstraints 处理。（在可以调用 transformExpr 之前，
	 * 我们无法处理原始表达式。）
	 *
	 * 我们现在可以在元组描述符中设置 atthasdef 标志；这只是节省了 StoreAttrDefault 
	 * 不必对 pg_attribute 行进行立即更新。
	 */
	fc_rawDefaults = NIL;
	fc_cookedDefaults = NIL;
	fc_attnum = 0;

	foreach(fc_listptr, fc_stmt->tableElts)
	{
		ColumnDef  *fc_colDef = lfirst(fc_listptr);
		Form_pg_attribute fc_attr;

		fc_attnum++;
		fc_attr = TupleDescAttr(fc_descriptor, fc_attnum - 1);

		if (fc_colDef->raw_default != NULL)
		{
			RawColumnDefault *fc_rawEnt;

			Assert(fc_colDef->cooked_default == NULL);

			fc_rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
			fc_rawEnt->attnum = fc_attnum;
			fc_rawEnt->raw_default = fc_colDef->raw_default;
			fc_rawEnt->missingMode = false;
			fc_rawEnt->generated = fc_colDef->generated;
			fc_rawDefaults = lappend(fc_rawDefaults, fc_rawEnt);
			fc_attr->atthasdef = true;
		}
		else if (fc_colDef->cooked_default != NULL)
		{
			CookedConstraint *fc_cooked;

			fc_cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint));
			fc_cooked->contype = CONSTR_DEFAULT;
			fc_cooked->conoid = InvalidOid;	/* 直到创建完成 */
			fc_cooked->name = NULL;
			fc_cooked->attnum = fc_attnum;
			fc_cooked->expr = fc_colDef->cooked_default;
			fc_cooked->skip_validation = false;
			fc_cooked->is_local = true;	/* 不用于默认值 */
			fc_cooked->inhcount = 0;	/* 同上 */
			fc_cooked->is_no_inherit = false;
			fc_cookedDefaults = lappend(fc_cookedDefaults, fc_cooked);
			fc_attr->atthasdef = true;
		}

		if (fc_colDef->identity)
			fc_attr->attidentity = fc_colDef->identity;

		if (fc_colDef->generated)
			fc_attr->attgenerated = fc_colDef->generated;

		if (fc_colDef->compression)
			fc_attr->attcompression = fc_GetAttributeCompression(fc_attr->atttypid,
														   fc_colDef->compression);
	}

	/*
	 * 如果语句没有指定访问方法，但我们正在定义
	 * 需要一个的关系类型，则使用默认值。
	 */
	if (fc_stmt->accessMethod != NULL)
	{
		fc_accessMethod = fc_stmt->accessMethod;

		if (fc_partitioned)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("specifying a table access method is not supported on a partitioned table")));
	}
	else if (RELKIND_HAS_TABLE_AM(fc_relkind))
		fc_accessMethod = default_table_access_method;

	/* 查找访问方法，验证它是否适用于表 */
	if (fc_accessMethod != NULL)
		fc_accessMethodId = get_table_am_oid(fc_accessMethod, false);

	/*
	 * 创建关系。 继承的默认值和约束立即传入
	 * 进行处理---由于它们不需要解析，可以立即存储。
	 */
	fc_relationId = heap_create_with_catalog(fc_relname,
										  fc_namespaceId,
										  fc_tablespaceId,
										  InvalidOid,
										  InvalidOid,
										  fc_ofTypeId,
										  fc_ownerId,
										  fc_accessMethodId,
										  fc_descriptor,
										  list_concat(fc_cookedDefaults,
													  fc_old_constraints),
										  fc_relkind,
										  fc_stmt->relation->relpersistence,
										  false,
										  false,
										  fc_stmt->oncommit,
										  fc_reloptions,
										  true,
										  allowSystemTableMods,
										  false,
										  InvalidOid,
										  fc_typaddress);

	/*
	 * 我们必须增加命令计数器，以使新创建的关系
	 * 元组在打开时可见。
	 */
	CommandCounterIncrement();

	/*
	 * 打开新关系并获取对其的独占锁。 这实际上并不
	 * 是为了阻止其他后端（因为在我们提交之前，他们
	 * 无法看到新的关系），但它可以防止锁管理器
	 * 抱怨死锁风险。
	 */
	fc_rel = relation_open(fc_relationId, AccessExclusiveLock);

	/*
	 * 现在将任何新指定的列默认值和生成表达式
	 * 添加到新关系中。这些以原始
	 * 解析树的形式传递给我们；我们需要将它们转换为可执行的表达式树
	 * 然后才能添加。最方便的方式是
	 * 应用解析器的transformExpr例程，但transformExpr在我们有
	 * 预先存在的关系之前是无法工作的。因此，转换必须在
	 * CREATE TABLE的最后一步中推迟。
	 *
	 * 这需要在处理分区子句之前，因为
	 * 这些可能会引用生成的列。
	 */
	if (fc_rawDefaults)
		AddRelationNewConstraints(fc_rel, fc_rawDefaults, NIL,
								  true, true, false, fc_queryString);

	/*
	 * 使列生成表达式可用于分区。
	 */
	CommandCounterIncrement();

	/* 处理并存储分区边界（如果有的话）。 */
	if (fc_stmt->partbound)
	{
		PartitionBoundSpec *fc_bound;
		ParseState *fc_pstate;
		Oid			fc_parentId = linitial_oid(fc_inheritOids),
					fc_defaultPartOid;
		Relation	fc_parent,
					fc_defaultRel = NULL;
		ParseNamespaceItem *fc_nsitem;

		/* 已经在父级上获得足够强的锁定 */
		fc_parent = table_open(fc_parentId, NoLock);

		/*
		 * 我们将尝试根据parentRel的分区键验证分区边界规范，
		 * 所以它最好有一个。
		 */
		if (fc_parent->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("\"%s\" is not partitioned",
							RelationGetRelationName(fc_parent))));

		/*
		 * 默认分区的分区约束依赖于
		 * 每个其他分区的分区边界。可能会有
		 * 其他后端即将对默认
		 * 分区表执行查询，并且该查询依赖于先前缓存的
		 * 默认分区约束。因此，我们必须获得一个足够强的表锁
		 * 以防止所有对默认分区的查询
		 * 在我们提交之前进行，并发送一个共享缓存无效通知
		 * 使它们更新索引列表。
		 *
		 * 锁定顺序：添加的关系在提交之前对
		 * 其他后端不可见，因此在
		 * DefineRelation()中，锁定默认分区和
		 * 正在添加的关系的顺序无关紧要。但在所有其他地方我们
		 * 需要在锁定要添加或移除的关系之前锁定默认关系，即
		 * 我们应该在所有地方以相同的顺序获取锁，
		 * 以避免死锁：锁定父级，锁定默认分区，然后
		 * 锁定分区。
		 */
		fc_defaultPartOid =
			get_default_oid_from_partdesc(RelationGetPartitionDesc(fc_parent,
																   true));
		if (OidIsValid(fc_defaultPartOid))
			fc_defaultRel = table_open(fc_defaultPartOid, AccessExclusiveLock);

		/* 转换边界值 */
		fc_pstate = make_parsestate(NULL);
		fc_pstate->p_sourcetext = fc_queryString;

		/*
		 * 添加一个包含此关系的nsitem，以便在对分区边界表达式调用transformExpr时能够使用适当的上下文报告错误。
		 */
		fc_nsitem = addRangeTableEntryForRelation(fc_pstate, fc_rel, AccessShareLock,
											   NULL, false, false);
		addNSItemToQuery(fc_pstate, fc_nsitem, false, true, true);

		fc_bound = transformPartitionBound(fc_pstate, fc_parent, fc_stmt->partbound);

		/*
		 * 首先检查新分区的边界是否有效，并且不与父分区的任何现有分区重叠。
		 */
		check_new_partition_bound(fc_relname, fc_parent, fc_bound, fc_pstate);

		/*
		 * 如果默认分区存在，则在添加这个新分区后，其分区约束将会改变，因此不允许任何符合该新分区的行。 所以，检查默认分区中现有数据是否满足在添加该分区后将存在的约束。
		 */
		if (OidIsValid(fc_defaultPartOid))
		{
			check_default_partition_contents(fc_parent, fc_defaultRel, fc_bound);
			/* 保持锁定直到提交。 */
			table_close(fc_defaultRel, NoLock);
		}

		/* 更新pg_class条目。 */
		StorePartitionBound(fc_rel, fc_parent, fc_bound);

		table_close(fc_parent, NoLock);
	}

	/* 存储新关系的继承信息。 */
	fc_StoreCatalogInheritance(fc_relationId, fc_inheritOids, fc_stmt->partbound != NULL);

	/*
	 * 处理分区规范（如果有的话），并将分区密钥信息存储到目录中。
	 */
	if (fc_partitioned)
	{
		ParseState *fc_pstate;
		char		fc_strategy;
		int			fc_partnatts;
		AttrNumber	fc_partattrs[PARTITION_MAX_KEYS];
		Oid			fc_partopclass[PARTITION_MAX_KEYS];
		Oid			fc_partcollation[PARTITION_MAX_KEYS];
		List	   *fc_partexprs = NIL;

		fc_pstate = make_parsestate(NULL);
		fc_pstate->p_sourcetext = fc_queryString;

		fc_partnatts = list_length(fc_stmt->partspec->partParams);

		/* 在这里和执行器中保护固定大小数组 */
		if (fc_partnatts > PARTITION_MAX_KEYS)
			ereport(ERROR,
					(errcode(ERRCODE_TOO_MANY_COLUMNS),
					 errmsg("cannot partition using more than %d columns",
							PARTITION_MAX_KEYS)));

		/*
		 * 我们需要将与分区表达式对应的原始解析树转换为可执行的表达式树。 像列默认值和CHECK约束一样，我们无法在早期进行该转换。
		 */
		fc_stmt->partspec = fc_transformPartitionSpec(fc_rel, fc_stmt->partspec,
												&fc_strategy);

		fc_ComputePartitionAttrs(fc_pstate, fc_rel, fc_stmt->partspec->partParams,
							  fc_partattrs, &fc_partexprs, fc_partopclass,
							  fc_partcollation, fc_strategy);

		StorePartitionKey(fc_rel, fc_strategy, fc_partnatts, fc_partattrs, fc_partexprs,
						  fc_partopclass, fc_partcollation);

		/* 使其全部可见 */
		CommandCounterIncrement();
	}

	/*
	 * 如果我们正在创建一个分区，现在创建父表中定义的所有索引、触发器、外键。
	 *
	 * 我们无法在早期执行此操作，因为DefineIndex想要知道我们刚刚存储的分区密钥。
	 */
	if (fc_stmt->partbound)
	{
		Oid			fc_parentId = linitial_oid(fc_inheritOids);
		Relation	fc_parent;
		List	   *fc_idxlist;
		ListCell   *fc_cell;

		/* 已经在父级上获得足够强的锁定 */
		fc_parent = table_open(fc_parentId, NoLock);
		fc_idxlist = RelationGetIndexList(fc_parent);

		/*
		 * 对于父表中的每个索引，在分区中创建一个。
		 */
		foreach(fc_cell, fc_idxlist)
		{
			Relation	fc_idxRel = index_open(lfirst_oid(fc_cell), AccessShareLock);
			AttrMap    *fc_attmap;
			IndexStmt  *fc_idxstmt;
			Oid			fc_constraintOid;

			if (fc_rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
			{
				if (fc_idxRel->rd_index->indisunique)
					ereport(ERROR,
							(errcode(ERRCODE_WRONG_OBJECT_TYPE),
							 errmsg("cannot create foreign partition of partitioned table \"%s\"",
									RelationGetRelationName(fc_parent)),
							 errdetail("Table \"%s\" contains indexes that are unique.",
									   RelationGetRelationName(fc_parent))));
				else
				{
					index_close(fc_idxRel, AccessShareLock);
					continue;
				}
			}

			fc_attmap = build_attrmap_by_name(RelationGetDescr(fc_rel),
										   RelationGetDescr(fc_parent));
			fc_idxstmt =
				generateClonedIndexStmt(NULL, fc_idxRel,
										fc_attmap, &fc_constraintOid);
			DefineIndex(RelationGetRelid(fc_rel),
						fc_idxstmt,
						InvalidOid,
						RelationGetRelid(fc_idxRel),
						fc_constraintOid,
						false, false, false, false, false);

			index_close(fc_idxRel, AccessShareLock);
		}

		list_free(fc_idxlist);

		/*
		 * 如果有任何行级触发器，将它们克隆到新分区中。
		 */
		if (fc_parent->trigdesc != NULL)
			fc_CloneRowTriggersToPartition(fc_parent, fc_rel);

		/*
		 * 和外键也是如此。 请注意，由于我们刚创建了表，因此不需要验证这些新约束。
		 */
		fc_CloneForeignKeyConstraints(NULL, fc_parent, fc_rel);

		table_close(fc_parent, NoLock);
	}

	/*
	 * 现在将任何新指定的CHECK约束添加到新关系中。 与上面的默认值相同，但这些需要在设置分区之后进行。
	 */
	if (fc_stmt->constraints)
		AddRelationNewConstraints(fc_rel, NIL, fc_stmt->constraints,
								  true, true, false, fc_queryString);

	ObjectAddressSet(fc_address, RelationRelationId, fc_relationId);

	/*
	 * 清理。 我们保持对新关系的锁定（尽管在提交之前它对其他任何人都不可见）。
	 */
	relation_close(fc_rel, NoLock);

	return fc_address;
}

/*
 * 对于在不存在的关系上发出的“DROP”命令发出正确的错误或警告消息
 */
static void fc_DropErrorMsgNonExistent(RangeVar *fc_rel, char fc_rightkind, bool fc_missing_ok)
{
	const struct dropmsgstrings *fc_rentry;

	if (fc_rel->schemaname != NULL &&
		!OidIsValid(LookupNamespaceNoError(fc_rel->schemaname)))
	{
		if (!fc_missing_ok)
		{
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_SCHEMA),
					 errmsg("schema \"%s\" does not exist", fc_rel->schemaname)));
		}
		else
		{
			ereport(NOTICE,
					(errmsg("schema \"%s\" does not exist, skipping",
							fc_rel->schemaname)));
		}
		return;
	}

	for (fc_rentry = dropmsgstringarray; fc_rentry->kind != '\0'; fc_rentry++)
	{
		if (fc_rentry->kind == fc_rightkind)
		{
			if (!fc_missing_ok)
			{
				ereport(ERROR,
						(errcode(fc_rentry->nonexistent_code),
						 errmsg(fc_rentry->nonexistent_msg, fc_rel->relname)));
			}
			else
			{
				ereport(NOTICE, (errmsg(fc_rentry->skipping_msg, fc_rel->relname)));
				break;
			}
		}
	}

	Assert(fc_rentry->kind != '\0');	/* 应该是不可能的 */
}

/*
 * 对于在错误类型关系上发出的“DROP”命令发出正确的错误消息
 */
static void fc_DropErrorMsgWrongType(const char *fc_relname, char fc_wrongkind, char fc_rightkind)
{
	const struct dropmsgstrings *fc_rentry;
	const struct dropmsgstrings *fc_wentry;

	for (fc_rentry = dropmsgstringarray; fc_rentry->kind != '\0'; fc_rentry++)
		if (fc_rentry->kind == fc_rightkind)
			break;
	Assert(fc_rentry->kind != '\0');

	for (fc_wentry = dropmsgstringarray; fc_wentry->kind != '\0'; fc_wentry++)
		if (fc_wentry->kind == fc_wrongkind)
			break;
	/* wrongkind可能是我们表中没有的某种... */

	ereport(ERROR,
			(errcode(ERRCODE_WRONG_OBJECT_TYPE),
			 errmsg(fc_rentry->nota_msg, fc_relname),
			 (fc_wentry->kind != '\0') ? errhint("%s", _(fc_wentry->drophint_msg)) : 0));
}

/*
 * RemoveRelations
 *		实现 DROP TABLE、DROP INDEX、DROP SEQUENCE、DROP VIEW、
 *		DROP MATERIALIZED VIEW、DROP FOREIGN TABLE
 */
void RemoveRelations(DropStmt *fc_drop)
{
	ObjectAddresses *fc_objects;
	char		fc_relkind;
	ListCell   *fc_cell;
	int			fc_flags = 0;
	LOCKMODE	fc_lockmode = AccessExclusiveLock;

	/* DROP CONCURRENTLY使用更弱的锁定，并且有一些限制 */
	if (fc_drop->concurrent)
	{
		/*
		 * 请注意，对于临时关系，此锁定可能稍后会升级，但由于没有其他会话可以访问临时关系，因此实际上这没有问题。
		 */
		fc_lockmode = ShareUpdateExclusiveLock;
		Assert(fc_drop->removeType == OBJECT_INDEX);
		if (list_length(fc_drop->objects) != 1)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("DROP INDEX CONCURRENTLY does not support dropping multiple objects")));
		if (fc_drop->behavior == DROP_CASCADE)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("DROP INDEX CONCURRENTLY does not support CASCADE")));
	}

	/*
	 * 首先我们识别所有的关系，然后在一次
	 * performMultipleDeletions() 调用中删除它们。这是为了避免
	 * 不必要的 DROP RESTRICT 错误，如果其中一个关系依赖于另一个。
	 */

	/* 确定所需的 relkind */
	switch (fc_drop->removeType)
	{
		case OBJECT_TABLE:
			fc_relkind = RELKIND_RELATION;
			break;

		case OBJECT_INDEX:
			fc_relkind = RELKIND_INDEX;
			break;

		case OBJECT_SEQUENCE:
			fc_relkind = RELKIND_SEQUENCE;
			break;

		case OBJECT_VIEW:
			fc_relkind = RELKIND_VIEW;
			break;

		case OBJECT_MATVIEW:
			fc_relkind = RELKIND_MATVIEW;
			break;

		case OBJECT_FOREIGN_TABLE:
			fc_relkind = RELKIND_FOREIGN_TABLE;
			break;

		default:
			elog(ERROR, "unrecognized drop object type: %d",
				 (int) fc_drop->removeType);
			fc_relkind = 0;		/* 保持编译器安静 */
			break;
	}

	/* 锁定并验证每个关系；构建对象地址列表 */
	fc_objects = new_object_addresses();

	foreach(fc_cell, fc_drop->objects)
	{
		RangeVar   *fc_rel = makeRangeVarFromNameList((List *) lfirst(fc_cell));
		Oid			fc_relOid;
		ObjectAddress fc_obj;
		struct DropRelationCallbackState fc_state;

		/*
		 * 接下来的几个步骤与 relation_openrv 非常相似，但我们
		 * 不用构建 relcache 条目，因为我们不需要它。
		 *
		 * 在尝试访问关系之前检查共享缓存无效消息。
		 * 这是为了覆盖这样的情况：名称标识的关系在
		 * 我们的事务开始后已被删除并重新创建：如果我们不刷新
		 * 旧的 syscache 条目，那么我们将附着于该条目，随后会出现错误。
		 */
		AcceptInvalidationMessages();

		/* 使用命名空间搜索查找适当的关系。 */
		fc_state.expected_relkind = fc_relkind;
		fc_state.heap_lockmode = fc_drop->concurrent ?
			ShareUpdateExclusiveLock : AccessExclusiveLock;
		/* 我们必须初始化这些字段以表明没有持有锁： */
		fc_state.heapOid = InvalidOid;
		fc_state.partParentOid = InvalidOid;

		fc_relOid = RangeVarGetRelidExtended(fc_rel, fc_lockmode, RVR_MISSING_OK,
										  fc_RangeVarCallbackForDropRelation,
										  (void *) &fc_state);

		/* 不在吗？ */
		if (!OidIsValid(fc_relOid))
		{
			fc_DropErrorMsgNonExistent(fc_rel, fc_relkind, fc_drop->missing_ok);
			continue;
		}

		/*
		 * 决定这里是否需要使用并发模式。回调为我们检索了
		 * 关系的持久性。
		 */
		if (fc_drop->concurrent &&
			fc_state.actual_relpersistence != RELPERSISTENCE_TEMP)
		{
			Assert(list_length(fc_drop->objects) == 1 &&
				   fc_drop->removeType == OBJECT_INDEX);
			fc_flags |= PERFORM_DELETION_CONCURRENTLY;
		}

		/*
		 * 分区索引的并发删除也无法使用。
		 */
		if ((fc_flags & PERFORM_DELETION_CONCURRENTLY) != 0 &&
			fc_state.actual_relkind == RELKIND_PARTITIONED_INDEX)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("cannot drop partitioned index \"%s\" concurrently",
							fc_rel->relname)));

		/*
		 * 如果我们被告知要删除一个分区索引，我们必须在继续之前
		 * 获得其父分区表所有子项的锁。否则我们会在它们的
		 * 表之前尝试锁定子索引分区，导致与其他会话潜在的
		 * 死锁，这些会话将以其他顺序锁定这些对象。
		 */
		if (fc_state.actual_relkind == RELKIND_PARTITIONED_INDEX)
			(void) find_all_inheritors(fc_state.heapOid,
									   fc_state.heap_lockmode,
									   NULL);

		/* 好的，我们准备删除这个 */
		fc_obj.classId = RelationRelationId;
		fc_obj.objectId = fc_relOid;
		fc_obj.objectSubId = 0;

		add_exact_object_address(&fc_obj, fc_objects);
	}

	performMultipleDeletions(fc_objects, fc_drop->behavior, fc_flags);

	free_object_addresses(fc_objects);
}

/*
 * 在获取表锁之前，检查我们是否有足够的权限。
 * 在 DROP INDEX 的情况下，也试图在索引之前锁定表。
 * 此外，如果要删除的表是一个分区，我们会先尝试锁定父项。
 */
static void fc_RangeVarCallbackForDropRelation(const RangeVar *fc_rel, Oid fc_relOid, Oid fc_oldRelOid,
								void *fc_arg)
{
	HeapTuple	fc_tuple;
	struct DropRelationCallbackState *fc_state;
	char		fc_expected_relkind;
	bool		fc_is_partition;
	Form_pg_class fc_classform;
	LOCKMODE	fc_heap_lockmode;
	bool		fc_invalid_system_index = false;

	fc_state = (struct DropRelationCallbackState *) fc_arg;
	fc_heap_lockmode = fc_state->heap_lockmode;

	/*
	 * 如果我们之前锁定了其他索引的堆，并且我们要查找的名称
	 * 不再指向该关系，则释放现在无用的锁。
	 */
	if (fc_relOid != fc_oldRelOid && OidIsValid(fc_state->heapOid))
	{
		UnlockRelationOid(fc_state->heapOid, fc_heap_lockmode);
		fc_state->heapOid = InvalidOid;
	}

	/*
	 * 类似地，如果我们之前锁定了其他分区的堆，并且我们要查找的
	 * 名称不再指向该关系，则释放现在无用的锁。
	 */
	if (fc_relOid != fc_oldRelOid && OidIsValid(fc_state->partParentOid))
	{
		UnlockRelationOid(fc_state->partParentOid, AccessExclusiveLock);
		fc_state->partParentOid = InvalidOid;
	}

	/* 没找到关系，因此无需锁定或权限检查。 */
	if (!OidIsValid(fc_relOid))
		return;

	fc_tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(fc_relOid));
	if (!HeapTupleIsValid(fc_tuple))
		return;					/* 并发删除，所以无需执行任何操作 */
	fc_classform = (Form_pg_class) GETSTRUCT(fc_tuple);
	fc_is_partition = fc_classform->relispartition;

	/* 传回一些数据以节省 RemoveRelations 中的查找 */
	fc_state->actual_relkind = fc_classform->relkind;
	fc_state->actual_relpersistence = fc_classform->relpersistence;

	/*
	 * RELKIND_RELATION 和 RELKIND_PARTITIONED_TABLE 都是 OBJECT_TABLE，
	 * 但 RemoveRelations() 只能对给定关系传递一个 relkind。
	 * 它为常规表和分区表选择 RELKIND_RELATION。
	 * 这意味着在关系为 RELKIND_PARTITIONED_TABLE 时，
	 * 我们必须小心以避免给出错误的类型错误。索引上也存在
	 * 同样的问题。
	 */
	if (fc_classform->relkind == RELKIND_PARTITIONED_TABLE)
		fc_expected_relkind = RELKIND_RELATION;
	else if (fc_classform->relkind == RELKIND_PARTITIONED_INDEX)
		fc_expected_relkind = RELKIND_INDEX;
	else
		fc_expected_relkind = fc_classform->relkind;

	if (fc_state->expected_relkind != fc_expected_relkind)
		fc_DropErrorMsgWrongType(fc_rel->relname, fc_classform->relkind,
							  fc_state->expected_relkind);

	/* 允许 DROP 给表拥有者或模式拥有者 */
	if (!pg_class_ownercheck(fc_relOid, GetUserId()) &&
		!pg_namespace_ownercheck(fc_classform->relnamespace, GetUserId()))
		aclcheck_error(ACLCHECK_NOT_OWNER,
					   get_relkind_objtype(fc_classform->relkind),
					   fc_rel->relname);

	/*
	 * 检查可能因失败的并发进程而失效的系统索引，并允许其删除。当前，这仅涉及在REINDEX CONCURRENTLY过程中失效的toast关系的索引。
	 */
	if (IsSystemClass(fc_relOid, fc_classform) && fc_classform->relkind == RELKIND_INDEX)
	{
		HeapTuple	fc_locTuple;
		Form_pg_index fc_indexform;
		bool		fc_indisvalid;

		fc_locTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(fc_relOid));
		if (!HeapTupleIsValid(fc_locTuple))
		{
			ReleaseSysCache(fc_tuple);
			return;
		}

		fc_indexform = (Form_pg_index) GETSTRUCT(fc_locTuple);
		fc_indisvalid = fc_indexform->indisvalid;
		ReleaseSysCache(fc_locTuple);

		/* 将对象标记为系统目录的失效索引 */
		if (!fc_indisvalid)
			fc_invalid_system_index = true;
	}

	/* 在失效索引的情况下，可以绕过此检查 */
	if (!fc_invalid_system_index && !allowSystemTableMods && IsSystemClass(fc_relOid, fc_classform))
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("permission denied: \"%s\" is a system catalog",
						fc_rel->relname)));

	ReleaseSysCache(fc_tuple);

	/*
	 * 在DROP INDEX中，尝试在锁定索引之前获取父表的锁。index_drop()无论如何都需要这样做，且由于常规查询在锁定索引之前锁定表，因此如果反过来进行，我们可能会面临死锁。如果没有找到pg_index条目，亦无错误——关系可能已经被删除。请注意，此代码将针对普通索引或分区索引执行。
	 */
	if (fc_expected_relkind == RELKIND_INDEX &&
		fc_relOid != fc_oldRelOid)
	{
		fc_state->heapOid = IndexGetRelation(fc_relOid, true);
		if (OidIsValid(fc_state->heapOid))
			LockRelationOid(fc_state->heapOid, fc_heap_lockmode);
	}

	/*
	 * 类似地，如果关系是分区，我们必须在锁定分区之前获取其父级的锁。这是因为查询先锁定父级，然后才锁定其分区，因此如果反过来进行，我们可能会面临死锁。
	 */
	if (fc_is_partition && fc_relOid != fc_oldRelOid)
	{
		fc_state->partParentOid = get_partition_parent(fc_relOid, true);
		if (OidIsValid(fc_state->partParentOid))
			LockRelationOid(fc_state->partParentOid, AccessExclusiveLock);
	}
}

/*
 * ExecuteTruncate
 *		执行TRUNCATE命令。
 *
 * 这是一个多关系的截断。我们首先打开并获取所有相关关系的独占锁，同时检查权限并验证关系是否适合截断。请注意，如果关系是外部表，此时我们尚未检查其外部数据源中的外部数据是否适合截断。这些将在稍后实际截断外部数据时进行检查。在CASCADE模式下，指向目标关系的外键引用的关系会自动添加到组中；在RESTRICT模式下，我们检查所有外键引用是否属于被截断的组。最后，所有关系都被截断并重新索引。
 */
void ExecuteTruncate(TruncateStmt *fc_stmt)
{
	List	   *fc_rels = NIL;
	List	   *fc_relids = NIL;
	List	   *fc_relids_logged = NIL;
	ListCell   *fc_cell;

	/*
	 * 打开、独占锁定并检查所有显式指定的关系
	 */
	foreach(fc_cell, fc_stmt->relations)
	{
		RangeVar   *fc_rv = lfirst(fc_cell);
		Relation	fc_rel;
		bool		fc_recurse = fc_rv->inh;
		Oid			fc_myrelid;
		LOCKMODE	fc_lockmode = AccessExclusiveLock;

		fc_myrelid = RangeVarGetRelidExtended(fc_rv, fc_lockmode,
										   0, fc_RangeVarCallbackForTruncate,
										   NULL);

		/* 对“TRUNCATE foo, foo”不抛出错误 */
		if (list_member_oid(fc_relids, fc_myrelid))
			continue;

		/* 打开关系，我们已经持有对它的锁 */
		fc_rel = table_open(fc_myrelid, NoLock);

		/*
		 * RangeVarGetRelidExtended() 已经通过其回调完成了大部分检查，
		 * 但对于现在已打开的关系，还有其他检查。
		 */
		fc_truncate_check_activity(fc_rel);

		fc_rels = lappend(fc_rels, fc_rel);
		fc_relids = lappend_oid(fc_relids, fc_myrelid);

		/* 仅在逻辑解码所需时记录此关系 */
		if (RelationIsLogicallyLogged(fc_rel))
			fc_relids_logged = lappend_oid(fc_relids_logged, fc_myrelid);

		if (fc_recurse)
		{
			ListCell   *fc_child;
			List	   *fc_children;

			fc_children = find_all_inheritors(fc_myrelid, fc_lockmode, NULL);

			foreach(fc_child, fc_children)
			{
				Oid			fc_childrelid = lfirst_oid(fc_child);

				if (list_member_oid(fc_relids, fc_childrelid))
					continue;

				/* find_all_inheritors 已经获得锁 */
				fc_rel = table_open(fc_childrelid, NoLock);

				/*
				 * 可能父表有子表，这些子表是其他后端的临时表。我们不能安全地访问这些表（因为缓冲问题），最好的做法是默默地忽略它们。请注意，此检查与下面调用的truncate_check_activity()中执行的检查相同，但它仍然保留在此以保持简单。
				 */
				if (RELATION_IS_OTHER_TEMP(fc_rel))
				{
					table_close(fc_rel, fc_lockmode);
					continue;
				}

				/*
				 * 继承的TRUNCATE命令只对父表执行访问权限检查。所以我们跳过对子表权限的检查，并且这里不调用truncate_check_perms()。
				 */
				fc_truncate_check_rel(RelationGetRelid(fc_rel), fc_rel->rd_rel);
				fc_truncate_check_activity(fc_rel);

				fc_rels = lappend(fc_rels, fc_rel);
				fc_relids = lappend_oid(fc_relids, fc_childrelid);

				/* 仅在逻辑解码所需时记录此关系 */
				if (RelationIsLogicallyLogged(fc_rel))
					fc_relids_logged = lappend_oid(fc_relids_logged, fc_childrelid);
			}
		}
		else if (fc_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("cannot truncate only a partitioned table"),
					 errhint("Do not specify the ONLY keyword, or use TRUNCATE ONLY on the partitions directly.")));
	}

	ExecuteTruncateGuts(fc_rels, fc_relids, fc_relids_logged,
						fc_stmt->behavior, fc_stmt->restart_seqs);

	/* 并关闭关系 */
	foreach(fc_cell, fc_rels)
	{
		Relation	fc_rel = (Relation) lfirst(fc_cell);

		table_close(fc_rel, NoLock);
	}
}

/*
 * ExecuteTruncateGuts
 *
 * TRUNCATE 的内部实现。这是由实际的 TRUNCATE
 * 命令调用的（见上文），以及执行
 * 复制 TRUNCATE 操作的复制订阅者。
 *
 * explicit_rels 是命令指定的要截断的关系列表。relids 是与 explicit_rels
 * 对应的 Oids 列表。relids_logged 是需要
 * WAL-日志记录的 Oids 列表（relids 的一个子集）。这一切都有点冗余，但现有的调用者以这种形式方便地掌握了这些信息。
 */
void ExecuteTruncateGuts(List *fc_explicit_rels,
					List *fc_relids,
					List *fc_relids_logged,
					DropBehavior fc_behavior, bool fc_restart_seqs)
{
	List	   *fc_rels;
	List	   *fc_seq_relids = NIL;
	HTAB	   *fc_ft_htab = NULL;
	EState	   *fc_estate;
	ResultRelInfo *fc_resultRelInfos;
	ResultRelInfo *fc_resultRelInfo;
	SubTransactionId fc_mySubid;
	ListCell   *fc_cell;
	Oid		   *fc_logrelids;

	/*
	 * 检查显式指定的关系。
	 *
	 * 在 CASCADE 模式下，吸纳所有引用关系。这
	 * 需要多次迭代来查找间接依赖关系。在每个阶段，我们需要在寻找其
	 * 依赖项之前排他性锁定新的关系，否则我们可能会错过一些东西。此外，我们在打开每个关系时会立即检查，以避免如在没有权限的关系上长时间持有锁这样的失误。
	 */
	fc_rels = list_copy(fc_explicit_rels);
	if (fc_behavior == DROP_CASCADE)
	{
		for (;;)
		{
			List	   *fc_newrelids;

			fc_newrelids = heap_truncate_find_FKs(fc_relids);
			if (fc_newrelids == NIL)
				break;			/* 没有其他要添加的内容 */

			foreach(fc_cell, fc_newrelids)
			{
				Oid			fc_relid = lfirst_oid(fc_cell);
				Relation	fc_rel;

				fc_rel = table_open(fc_relid, AccessExclusiveLock);
				ereport(NOTICE,
						(errmsg("truncate cascades to table \"%s\"",
								RelationGetRelationName(fc_rel))));
				fc_truncate_check_rel(fc_relid, fc_rel->rd_rel);
				fc_truncate_check_perms(fc_relid, fc_rel->rd_rel);
				fc_truncate_check_activity(fc_rel);
				fc_rels = lappend(fc_rels, fc_rel);
				fc_relids = lappend_oid(fc_relids, fc_relid);

				/* 仅在逻辑解码所需时记录此关系 */
				if (RelationIsLogicallyLogged(fc_rel))
					fc_relids_logged = lappend_oid(fc_relids_logged, fc_relid);
			}
		}
	}

	/*
	 * 检查外键引用。在 CASCADE 模式下，这应该是
	 * 不必要的，因为我们刚刚拉入了所有引用；但作为
	 * 交叉检查，如果是在启用 Assert 的构建中，还是要执行此操作。
	 */
#ifdef USE_ASSERT_CHECKING
	heap_truncate_check_FKs(fc_rels, false);
#else
	if (fc_behavior == DROP_RESTRICT)
		heap_truncate_check_FKs(fc_rels, false);
#endif

	/*
	 * 如果我们被要求重新启动序列，找到所有序列，锁定它们
	 * （我们需要重置序列的 AccessExclusiveLock），并检查权限。
	 * 我们希望尽早执行此操作，因为仅仅执行所有截断
	 * 工作却在序列权限上失败是毫无意义的。
	 */
	if (fc_restart_seqs)
	{
		foreach(fc_cell, fc_rels)
		{
			Relation	fc_rel = (Relation) lfirst(fc_cell);
			List	   *fc_seqlist = getOwnedSequences(RelationGetRelid(fc_rel));
			ListCell   *fc_seqcell;

			foreach(fc_seqcell, fc_seqlist)
			{
				Oid			fc_seq_relid = lfirst_oid(fc_seqcell);
				Relation	fc_seq_rel;

				fc_seq_rel = relation_open(fc_seq_relid, AccessExclusiveLock);

				/* 此检查必须与 AlterSequence 匹配！ */
				if (!pg_class_ownercheck(fc_seq_relid, GetUserId()))
					aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SEQUENCE,
								   RelationGetRelationName(fc_seq_rel));

				fc_seq_relids = lappend_oid(fc_seq_relids, fc_seq_relid);

				relation_close(fc_seq_rel, NoLock);
			}
		}
	}

	/* 准备捕获 AFTER 触发器。 */
	AfterTriggerBeginQuery();

	/*
	 * 为了触发触发器，我们需要每个关系的 EState 以及 ResultRelInfo。
	 * 不过我们不需要调用 ExecOpenIndices。
	 *
	 * 我们将 ResultRelInfos 放在 es_opened_result_relations 列表中，即使
	 * 我们没有范围表，也没有填充 es_result_relations 数组。这有点假，但足以让
	 * ExecGetTriggerResultRel() 找到它们。
	 */
	fc_estate = CreateExecutorState();
	fc_resultRelInfos = (ResultRelInfo *)
		palloc(list_length(fc_rels) * sizeof(ResultRelInfo));
	fc_resultRelInfo = fc_resultRelInfos;
	foreach(fc_cell, fc_rels)
	{
		Relation	fc_rel = (Relation) lfirst(fc_cell);

		InitResultRelInfo(fc_resultRelInfo,
						  fc_rel,
						  0,	/* 虚拟的范围表索引 */
						  NULL,
						  0);
		fc_estate->es_opened_result_relations =
			lappend(fc_estate->es_opened_result_relations, fc_resultRelInfo);
		fc_resultRelInfo++;
	}

	/*
	 * 在开始截断之前处理所有 BEFORE STATEMENT TRUNCATE 触发器
	 * （这是因为其中一个可能会抛出错误）。此外，如果
	 * 我们允许它们阻止语句执行，这需要在此处理。
	 */
	fc_resultRelInfo = fc_resultRelInfos;
	foreach(fc_cell, fc_rels)
	{
		ExecBSTruncateTriggers(fc_estate, fc_resultRelInfo);
		fc_resultRelInfo++;
	}

	/*
	 * 好吧，截断每个表。
	 */
	fc_mySubid = GetCurrentSubTransactionId();

	foreach(fc_cell, fc_rels)
	{
		Relation	fc_rel = (Relation) lfirst(fc_cell);

		/* 跳过分区表，因为没有要做的事情 */
		if (fc_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
			continue;

		/*
		 * 构建属于每个外部服务器的外部表列表，并将每个列表传递给外部数据包装器的回调函数，
		 * 以便每个服务器可以批量截断其所有外部表。
		 * 每个列表作为哈希表中的单个条目保存，使用
		 * 服务器 OID 作为查找键。
		 */
		if (fc_rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
		{
			Oid			fc_serverid = GetForeignServerIdByRelId(RelationGetRelid(fc_rel));
			bool		fc_found;
			ForeignTruncateInfo *fc_ft_info;

			/* 第一次通过，为外部表初始化哈希表 */
			if (!fc_ft_htab)
			{
				HASHCTL		fc_hctl;

				memset(&fc_hctl, 0, sizeof(HASHCTL));
				fc_hctl.keysize = sizeof(Oid);
				fc_hctl.entrysize = sizeof(ForeignTruncateInfo);
				fc_hctl.hcxt = CurrentMemoryContext;

				fc_ft_htab = hash_create("TRUNCATE for Foreign Tables",
									  32,	/* 从小开始并扩展 */
									  &fc_hctl,
									  HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
			}

			/* 查找或创建外部表的缓存条目 */
			fc_ft_info = hash_search(fc_ft_htab, &fc_serverid, HASH_ENTER, &fc_found);
			if (!fc_found)
			{
				fc_ft_info->serverid = fc_serverid;
				fc_ft_info->rels = NIL;
			}

			/*
			 * 将外部表保存在服务器的条目中，该外部表属于该服务器。
			 */
			fc_ft_info->rels = lappend(fc_ft_info->rels, fc_rel);
			continue;
		}

		/*
		 * 通常，我们在这里需要一个事务安全的截断。然而，如果
		 * 表是在当前（子）事务中创建的或在当前（子）事务中拥有
		 * 新的 relfilenode，那么我们可以直接在原地截断，因为
		 * 回滚会导致整个表或当前物理文件被丢弃。
		 */
		if (fc_rel->rd_createSubid == fc_mySubid ||
			fc_rel->rd_newRelfilenodeSubid == fc_mySubid)
		{
			/* 立即的、不可回滚的截断是可以的 */
			heap_truncate_one_rel(fc_rel);
		}
		else
		{
			Oid			fc_heap_relid;
			Oid			fc_toast_relid;
			ReindexParams fc_reindex_params = {0};

			/*
			 * 这实际上会删除表中的所有行，并且可以在
			 * 可序列化交易中完成。在这种情况下，我们必须记录一个
			 * rw-conflict，在持有对表的谓词锁的每个事务中
			 * 到这个事务。
			 */
			CheckTableForSerializableConflictIn(fc_rel);

			/*
			 * 需要完整的事务安全的推送。
			 *
			 * 为关系创建一个新的空存储文件，并将其分配为
			 * relfilenode 值。旧的存储文件在提交时安排删除。
			 */
			RelationSetNewRelfilenode(fc_rel, fc_rel->rd_rel->relpersistence);

			fc_heap_relid = RelationGetRelid(fc_rel);

			/*
			 * 如果有的话，对 toast 表同样适用。
			 */
			fc_toast_relid = fc_rel->rd_rel->reltoastrelid;
			if (OidIsValid(fc_toast_relid))
			{
				Relation	fc_toastrel = relation_open(fc_toast_relid,
													 AccessExclusiveLock);

				RelationSetNewRelfilenode(fc_toastrel,
										  fc_toastrel->rd_rel->relpersistence);
				table_close(fc_toastrel, NoLock);
			}

			/*
			 * 重新构建索引以匹配，我们就完成了。
			 */
			reindex_relation(fc_heap_relid, REINDEX_REL_PROCESS_TOAST,
							 &fc_reindex_params);
		}

		pgstat_count_truncate(fc_rel);
	}

	/* 现在遍历哈希表，并截断外部表 */
	if (fc_ft_htab)
	{
		ForeignTruncateInfo *fc_ft_info;
		HASH_SEQ_STATUS fc_seq;

		hash_seq_init(&fc_seq, fc_ft_htab);

		PG_TRY();
		{
			while ((fc_ft_info = hash_seq_search(&fc_seq)) != NULL)
			{
				FdwRoutine *fc_routine = GetFdwRoutineByServerId(fc_ft_info->serverid);

				/* truncate_check_rel() 已经检查过这一点 */
				Assert(fc_routine->ExecForeignTruncate != NULL);

				fc_routine->ExecForeignTruncate(fc_ft_info->rels,
											 fc_behavior,
											 fc_restart_seqs);
			}
		}
		PG_FINALLY();
		{
			hash_destroy(fc_ft_htab);
		}
		PG_END_TRY();
	}

	/*
	 * 如果我们被要求，重启拥有的序列。
	 */
	foreach(fc_cell, fc_seq_relids)
	{
		Oid			fc_seq_relid = lfirst_oid(fc_cell);

		ResetSequence(fc_seq_relid);
	}

	/*
	 * 写入一个 WAL 记录，以允许这一组动作被逻辑
	 * 解码。
	 *
	 * 组装一个 relids 数组，以便我们可以为整个
	 * 操作写入一个单一的 WAL 记录。
	 */
	if (list_length(fc_relids_logged) > 0)
	{
		xl_heap_truncate fc_xlrec;
		int			fc_i = 0;

		/* 应该只有在 wal_level >= logical 时才能到达这里 */
		Assert(XLogLogicalInfoActive());

		fc_logrelids = palloc(list_length(fc_relids_logged) * sizeof(Oid));
		foreach(fc_cell, fc_relids_logged)
			fc_logrelids[fc_i++] = lfirst_oid(fc_cell);

		fc_xlrec.dbId = MyDatabaseId;
		fc_xlrec.nrelids = list_length(fc_relids_logged);
		fc_xlrec.flags = 0;
		if (fc_behavior == DROP_CASCADE)
			fc_xlrec.flags |= XLH_TRUNCATE_CASCADE;
		if (fc_restart_seqs)
			fc_xlrec.flags |= XLH_TRUNCATE_RESTART_SEQS;

		XLogBeginInsert();
		XLogRegisterData((char *) &fc_xlrec, SizeOfHeapTruncate);
		XLogRegisterData((char *) fc_logrelids, list_length(fc_relids_logged) * sizeof(Oid));

		XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);

		(void) XLogInsert(RM_HEAP_ID, XLOG_HEAP_TRUNCATE);
	}

	/*
	 * 处理所有 AFTER STATEMENT TRUNCATE 触发器。
	 */
	fc_resultRelInfo = fc_resultRelInfos;
	foreach(fc_cell, fc_rels)
	{
		ExecASTruncateTriggers(fc_estate, fc_resultRelInfo);
		fc_resultRelInfo++;
	}

	/* 处理排队的 AFTER 触发器 */
	AfterTriggerEndQuery(fc_estate);

	/* 我们现在可以清理 EState 了 */
	FreeExecutorState(fc_estate);

	/*
	 * 关闭任何由 CASCADE 打开的关系（在 EState 仍然
	 * 持有引用时无法执行此操作）
	 */
	fc_rels = list_difference_ptr(fc_rels, fc_explicit_rels);
	foreach(fc_cell, fc_rels)
	{
		Relation	fc_rel = (Relation) lfirst(fc_cell);

		table_close(fc_rel, NoLock);
	}
}

/*
 * 检查给定关系是否安全以便截断。ExecuteTruncate() 和
 * RangeVarCallbackForTruncate() 的子例程。
 */
static void fc_truncate_check_rel(Oid fc_relid, Form_pg_class fc_reltuple)
{
	char	   *fc_relname = NameStr(fc_reltuple->relname);

	/*
	 * 只允许在常规表、使用支持 TRUNCATE 的外部数据
	 * 封装器的外部表和分区表上进行截断（尽管，后者
	 * 仅在此处包括以进行以下检查；在它们的情况下不会发生
	 * 物理截断）。
	 */
	if (fc_reltuple->relkind == RELKIND_FOREIGN_TABLE)
	{
		Oid			fc_serverid = GetForeignServerIdByRelId(fc_relid);
		FdwRoutine *fc_fdwroutine = GetFdwRoutineByServerId(fc_serverid);

		if (!fc_fdwroutine->ExecForeignTruncate)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("cannot truncate foreign table \"%s\"",
							fc_relname)));
	}
	else if (fc_reltuple->relkind != RELKIND_RELATION &&
			 fc_reltuple->relkind != RELKIND_PARTITIONED_TABLE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("\"%s\" is not a table", fc_relname)));

	/*
	 * 大多数系统目录根本无法截断，或者至少在
	 * allow_system_table_mods=on 的情况下才能截断。然而，作为
	 * 例外，我们允许在 pg_upgrade 中截断 pg_largeobject，因为
	 * 我们需要更改其 relfilenode 以匹配旧集群，并且允许执行
	 * TRUNCATE 命令是最简单的方式。
	 */
	if (!allowSystemTableMods && IsSystemClass(fc_relid, fc_reltuple)
		&& (!IsBinaryUpgrade || fc_relid != LargeObjectRelationId))
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("permission denied: \"%s\" is a system catalog",
						fc_relname)));

	InvokeObjectTruncateHook(fc_relid);
}

/*
 * 检查当前用户是否有权限截断给定关系。
 */
static void fc_truncate_check_perms(Oid fc_relid, Form_pg_class fc_reltuple)
{
	char	   *fc_relname = NameStr(fc_reltuple->relname);
	AclResult	fc_aclresult;

	/* 权限检查 */
	fc_aclresult = pg_class_aclcheck(fc_relid, GetUserId(), ACL_TRUNCATE);
	if (fc_aclresult != ACLCHECK_OK)
		aclcheck_error(fc_aclresult, get_relkind_objtype(fc_reltuple->relkind),
					   fc_relname);
}

/*
 * 一组额外的有效性检查，用于检查给定的关系是否安全进行
 * 截断。 这与 truncate_check_rel() 进行分割，因为
 * RangeVarCallbackForTruncate() 还不能打开一个关系。
 */
static void fc_truncate_check_activity(Relation fc_rel)
{
	/*
	 * 不允许在其他后端的临时表上进行截断 ... 它们的本地
	 * 缓冲区管理器无法处理。
	 */
	if (RELATION_IS_OTHER_TEMP(fc_rel))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot truncate temporary tables of other sessions")));

	/*
	 * 还要检查当前事务中对关系的活跃使用，
	 * 包括开放扫描和待处理的 AFTER 触发器事件。
	 */
	CheckTableNotInUse(fc_rel, "TRUNCATE");
}

/*
 * storage_name
 *	  返回与 typstorage/attstorage 枚举值对应的名称
 */
static const char * fc_storage_name(char fc_c)
{
	switch (fc_c)
	{
		case TYPSTORAGE_PLAIN:
			return "PLAIN";
		case TYPSTORAGE_EXTERNAL:
			return "EXTERNAL";
		case TYPSTORAGE_EXTENDED:
			return "EXTENDED";
		case TYPSTORAGE_MAIN:
			return "MAIN";
		default:
			return "???";
	}
}

/*----------
 * MergeAttributes
 *		根据初始架构和超类返回新的架构。
 *
 * 输入参数：
 * 'schema' 是表的列/属性定义。 （这是一个 ColumnDef 的列表。）它是破坏性更改的。
 * 'supers' 是父关系的 OID 列表，由调用者已经锁定。
 * 'relpersistence' 是表的持久性类型。
 * 'is_partition' 表示表是否为分区。
 *
 * 输出参数：
 * 'supconstr' 接收属于父类的约束列表，
 *		根据需要更新以便在子类中有效。
 *
 * 返回值：
 * 完成的架构列表。
 *
 * 注意：
 *	  属性的继承顺序非常重要。
 *	  直观上，继承的属性应该放在前面。如果一个表
 *	  从多个父类继承属性，则这些属性的顺序
 *	  根据在 CREATE TABLE 中指定的父类顺序。
 *
 *	  这是一个示例：
 *
 *		create table person (name text, age int4, location point);
 *		create table emp (salary int4, manager text) inherits(person);
 *		create table student (gpa float8) inherits (person);
 *		create table stud_emp (percent int4) inherits (emp, student);
 *
 *	  stud_emp 的属性顺序是：
 *
 *							person {1:name, 2:age, 3:location}
 *							/	 \
 *			   {6:gpa}	student   emp {4:salary, 5:manager}
 *							\	 /
 *						   stud_emp {7:percent}
 *
 *	   如果同一属性名称出现多次，则它在结果表中的位置
 *	   是其首次出现的正确位置。
 *
 *	   子表的约束（包括 NOT NULL 约束）
 *	   是来自子架构和父表的所有相关约束的并集。
 *
 *	   子列的默认值定义为：
 *		(1) 如果子架构指定了默认值，则使用该值。
 *		(2) 如果子架构和任何父类都未指定默认值，则
 *			该列将没有默认值。
 *		(3) 如果从不同的父类继承了冲突的默认值
 *			（且未被子类覆盖），则会引发错误。
 *		(4) 否则使用继承的默认值。
 *		规则 (3) 在 Postgres 7.1 中为新功能；在早期版本中，
 *		您可以相对任意地选择使用哪个父类的默认值。
 *----------
 */
static List * fc_MergeAttributes(List *fc_schema, List *fc_supers, char fc_relpersistence,
				bool fc_is_partition, List **fc_supconstr)
{
	List	   *fc_inhSchema = NIL;
	List	   *fc_constraints = NIL;
	bool		fc_have_bogus_defaults = false;
	int			fc_child_attno;
	static Node fc_bogus_marker = {0}; /* 标记冲突的默认值 */
	List	   *fc_saved_schema = NIL;
	ListCell   *fc_entry;

	/*
	 * 检查并拒绝列数过多的表。我们相对较早地进行此检查有两个原因：(a) 我们不冒险在后续代码中溢出 AttrNumber (b) 如果我们处理的列数 <= 1600，O(n^2) 算法是可以的，但如果用户尝试创建具有数十万列的表，可能会花费几分钟的时间执行。
	 *
	 * 请注意，在包含来自继承关系的列后，我们还需要检查是否超过此数字。
	 */
	if (list_length(fc_schema) > MaxHeapAttributeNumber)
		ereport(ERROR,
				(errcode(ERRCODE_TOO_MANY_COLUMNS),
				 errmsg("tables can have at most %d columns",
						MaxHeapAttributeNumber)));

	/*
	 * 检查显式属性列表中是否有重复名称。
	 *
	 * 尽管我们可能考虑合并这些条目，就像处理继承属性的名称冲突一样，但假设此类冲突是错误的似乎更有意义。
	 *
	 * 此处我们不使用 foreach()，因为我们对模式列表有两个嵌套循环，并且可能在内部循环中进行元素删除。如果我们使用 foreach_delete_current()，它只能修复其中一个循环的状态，因此在两个循环中使用遍历列表索引似乎更干净。请注意，任何删除都发生在外部循环所处位置之后，因此其索引不需要调整。
	 */
	for (int fc_coldefpos = 0; fc_coldefpos < list_length(fc_schema); fc_coldefpos++)
	{
		ColumnDef  *fc_coldef = list_nth_node(ColumnDef, fc_schema, fc_coldefpos);

		if (!fc_is_partition && fc_coldef->typeName == NULL)
		{
			/*
			 * 类型表列选项不属于类型的列。这是可行的，因为来自类型的列在列表中首先出现。（我们对分区列列表省略此检查；这些在下面单独处理。）
			 */
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_COLUMN),
					 errmsg("column \"%s\" does not exist",
							fc_coldef->colname)));
		}

		/* restpos 扫描 coldef 之后的所有条目； incr 位于循环体内 */
		for (int fc_restpos = fc_coldefpos + 1; fc_restpos < list_length(fc_schema);)
		{
			ColumnDef  *fc_restdef = list_nth_node(ColumnDef, fc_schema, fc_restpos);

			if (strcmp(fc_coldef->colname, fc_restdef->colname) == 0)
			{
				if (fc_coldef->is_from_type)
				{
					/*
					 * 将列选项合并到来自类型的列中
					 */
					fc_coldef->is_not_null = fc_restdef->is_not_null;
					fc_coldef->raw_default = fc_restdef->raw_default;
					fc_coldef->cooked_default = fc_restdef->cooked_default;
					fc_coldef->constraints = fc_restdef->constraints;
					fc_coldef->is_from_type = false;
					fc_schema = list_delete_nth_cell(fc_schema, fc_restpos);
				}
				else
					ereport(ERROR,
							(errcode(ERRCODE_DUPLICATE_COLUMN),
							 errmsg("column \"%s\" specified more than once",
									fc_coldef->colname)));
			}
			else
				fc_restpos++;
		}
	}

	/*
	 * 在分区的情况下，没有新的列定义，只有为列约束创建的虚拟 ColumnDefs。现在将它们搁置，并在最后处理它们。
	 */
	if (fc_is_partition)
	{
		fc_saved_schema = fc_schema;
		fc_schema = NIL;
	}

	/*
	 * 从左到右扫描父项，并合并它们的属性以形成继承属性列表 (inhSchema)。还检查是否需要继承 OID 列。
	 */
	fc_child_attno = 0;
	foreach(fc_entry, fc_supers)
	{
		Oid			fc_parent = lfirst_oid(fc_entry);
		Relation	fc_relation;
		TupleDesc	fc_tupleDesc;
		TupleConstr *fc_constr;
		AttrMap    *fc_newattmap;
		List	   *fc_inherited_defaults;
		List	   *fc_cols_with_defaults;
		AttrNumber	fc_parent_attno;
		ListCell   *fc_lc1;
		ListCell   *fc_lc2;

		/* 调用者已获得锁定 */
		fc_relation = table_open(fc_parent, NoLock);

		/*
		 * 检查当前事务中父分区表的活动使用情况，例如是否被某个封闭命令以某种方式使用。
		 */
		if (fc_is_partition)
			CheckTableNotInUse(fc_relation, "CREATE TABLE .. PARTITION OF");

		/*
		 * 我们不允许分区表和分区参与常规继承。
		 */
		if (fc_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
			!fc_is_partition)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("cannot inherit from partitioned table \"%s\"",
							RelationGetRelationName(fc_relation))));
		if (fc_relation->rd_rel->relispartition && !fc_is_partition)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("cannot inherit from partition \"%s\"",
							RelationGetRelationName(fc_relation))));

		if (fc_relation->rd_rel->relkind != RELKIND_RELATION &&
			fc_relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
			fc_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("inherited relation \"%s\" is not a table or foreign table",
							RelationGetRelationName(fc_relation))));

		/*
		 * 如果父项是永久性的，则它的所有分区也必须是永久性的。请注意，继承允许这种情况。
		 */
		if (fc_is_partition &&
			fc_relation->rd_rel->relpersistence != RELPERSISTENCE_TEMP &&
			fc_relpersistence == RELPERSISTENCE_TEMP)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("cannot create a temporary relation as partition of permanent relation \"%s\"",
							RelationGetRelationName(fc_relation))));

		/* 永久关系不能从临时关系中继承 */
		if (fc_relpersistence != RELPERSISTENCE_TEMP &&
			fc_relation->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg(!fc_is_partition
							? "cannot inherit from temporary relation \"%s\""
							: "cannot create a permanent relation as partition of temporary relation \"%s\"",
							RelationGetRelationName(fc_relation))));

		/* 如果现有关系是临时的，它必须属于此会话 */
		if (fc_relation->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
			!fc_relation->rd_islocaltemp)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg(!fc_is_partition
							? "cannot inherit from temporary relation of another session"
							: "cannot create as partition of temporary relation of another session")));

		/*
		 * 我们应该为此有一个 UNDER 权限标志，但目前要求子表的创建者拥有父表。
		 */
		if (!pg_class_ownercheck(RelationGetRelid(fc_relation), GetUserId()))
			aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(fc_relation->rd_rel->relkind),
						   RelationGetRelationName(fc_relation));

		fc_tupleDesc = RelationGetDescr(fc_relation);
		fc_constr = fc_tupleDesc->constr;

		/*
		 * newattmap->attnums[] 将包含该父表属性的子表属性编号。（对于第一个父表之后的父表，或者如果我们删除了列，它们是不一样的。）
		 */
		fc_newattmap = make_attrmap(fc_tupleDesc->natts);

		/* 我们不能处理继承的默认值，直到 newattmap 完成。 */
		fc_inherited_defaults = fc_cols_with_defaults = NIL;

		for (fc_parent_attno = 1; fc_parent_attno <= fc_tupleDesc->natts;
			 fc_parent_attno++)
		{
			Form_pg_attribute fc_attribute = TupleDescAttr(fc_tupleDesc,
														fc_parent_attno - 1);
			char	   *fc_attributeName = NameStr(fc_attribute->attname);
			int			fc_exist_attno;
			ColumnDef  *fc_def;

			/*
			 * 忽略父级中删除的列。
			 */
			if (fc_attribute->attisdropped)
				continue;		/* 将 newattmap->attnums 条目保留为零 */

			/*
			 * 它是否与某个先前继承的列冲突？
			 */
			fc_exist_attno = fc_findAttrByName(fc_attributeName, fc_inhSchema);
			if (fc_exist_attno > 0)
			{
				Oid			fc_defTypeId;
				int32		fc_deftypmod;
				Oid			fc_defCollId;

				/*
				 * 是的，尝试合并这两个列定义。它们必须
				 * 具有相同的类型、typmod 和排序规则。
				 */
				ereport(NOTICE,
						(errmsg("merging multiple inherited definitions of column \"%s\"",
								fc_attributeName)));
				fc_def = (ColumnDef *) list_nth(fc_inhSchema, fc_exist_attno - 1);
				typenameTypeIdAndMod(NULL, fc_def->typeName, &fc_defTypeId, &fc_deftypmod);
				if (fc_defTypeId != fc_attribute->atttypid ||
					fc_deftypmod != fc_attribute->atttypmod)
					ereport(ERROR,
							(errcode(ERRCODE_DATATYPE_MISMATCH),
							 errmsg("inherited column \"%s\" has a type conflict",
									fc_attributeName),
							 errdetail("%s versus %s",
									   format_type_with_typemod(fc_defTypeId,
																fc_deftypmod),
									   format_type_with_typemod(fc_attribute->atttypid,
																fc_attribute->atttypmod))));
				fc_defCollId = GetColumnDefCollation(NULL, fc_def, fc_defTypeId);
				if (fc_defCollId != fc_attribute->attcollation)
					ereport(ERROR,
							(errcode(ERRCODE_COLLATION_MISMATCH),
							 errmsg("inherited column \"%s\" has a collation conflict",
									fc_attributeName),
							 errdetail("\"%s\" versus \"%s\"",
									   get_collation_name(fc_defCollId),
									   get_collation_name(fc_attribute->attcollation))));

				/* 复制/检查存储参数 */
				if (fc_def->storage == 0)
					fc_def->storage = fc_attribute->attstorage;
				else if (fc_def->storage != fc_attribute->attstorage)
					ereport(ERROR,
							(errcode(ERRCODE_DATATYPE_MISMATCH),
							 errmsg("inherited column \"%s\" has a storage parameter conflict",
									fc_attributeName),
							 errdetail("%s versus %s",
									   fc_storage_name(fc_def->storage),
									   fc_storage_name(fc_attribute->attstorage))));

				/* 复制/检查压缩参数 */
				if (CompressionMethodIsValid(fc_attribute->attcompression))
				{
					const char *fc_compression =
					GetCompressionMethodName(fc_attribute->attcompression);

					if (fc_def->compression == NULL)
						fc_def->compression = pstrdup(fc_compression);
					else if (strcmp(fc_def->compression, fc_compression) != 0)
						ereport(ERROR,
								(errcode(ERRCODE_DATATYPE_MISMATCH),
								 errmsg("column \"%s\" has a compression method conflict",
										fc_attributeName),
								 errdetail("%s versus %s", fc_def->compression, fc_compression)));
				}

				fc_def->inhcount++;
				/* 合并 NOT NULL 约束 = 或将其“与”在一起 */
				fc_def->is_not_null |= fc_attribute->attnotnull;
				/* 默认值和其他约束在下面处理 */
				fc_newattmap->attnums[fc_parent_attno - 1] = fc_exist_attno;

				/* 检查 GENERATED 冲突 */
				if (fc_def->generated != fc_attribute->attgenerated)
					ereport(ERROR,
							(errcode(ERRCODE_DATATYPE_MISMATCH),
							 errmsg("inherited column \"%s\" has a generation conflict",
									fc_attributeName)));
			}
			else
			{
				/*
				 * 不，创建一个新的继承列
				 */
				fc_def = makeNode(ColumnDef);
				fc_def->colname = pstrdup(fc_attributeName);
				fc_def->typeName = makeTypeNameFromOid(fc_attribute->atttypid,
													fc_attribute->atttypmod);
				fc_def->inhcount = 1;
				fc_def->is_local = false;
				fc_def->is_not_null = fc_attribute->attnotnull;
				fc_def->is_from_type = false;
				fc_def->storage = fc_attribute->attstorage;
				fc_def->raw_default = NULL;
				fc_def->cooked_default = NULL;
				fc_def->generated = fc_attribute->attgenerated;
				fc_def->collClause = NULL;
				fc_def->collOid = fc_attribute->attcollation;
				fc_def->constraints = NIL;
				fc_def->location = -1;
				if (CompressionMethodIsValid(fc_attribute->attcompression))
					fc_def->compression =
						pstrdup(GetCompressionMethodName(fc_attribute->attcompression));
				else
					fc_def->compression = NULL;
				fc_inhSchema = lappend(fc_inhSchema, fc_def);
				fc_newattmap->attnums[fc_parent_attno - 1] = ++fc_child_attno;
			}

			/*
			 * 如果有，定位默认值
			 */
			if (fc_attribute->atthasdef)
			{
				Node	   *fc_this_default = NULL;

				/* 在约束结构中寻找默认值 */
				if (fc_constr != NULL)
				{
					AttrDefault *fc_attrdef = fc_constr->defval;

					for (int fc_i = 0; fc_i < fc_constr->num_defval; fc_i++)
					{
						if (fc_attrdef[fc_i].adnum == fc_parent_attno)
						{
							fc_this_default = stringToNode(fc_attrdef[fc_i].adbin);
							break;
						}
					}
				}
				if (fc_this_default == NULL)
					elog(ERROR, "default expression not found for attribute %d of relation \"%s\"",
						 fc_parent_attno, RelationGetRelationName(fc_relation));

				/*
				 * 如果它是 GENERATED 默认值，它可能包含 Vars
				 * 需要映射到继承列的新编号。
				 * 我们不能在 newattmap 准备好之前这样做，所以请记住
				 * 所有继承的默认表达式。
				 */
				fc_inherited_defaults = lappend(fc_inherited_defaults, fc_this_default);
				fc_cols_with_defaults = lappend(fc_cols_with_defaults, fc_def);
			}
		}

		/*
		 * 现在处理任何继承的默认表达式，使用完成的 newattmap 映射调整 attnos。
		 */
		forboth(fc_lc1, fc_inherited_defaults, fc_lc2, fc_cols_with_defaults)
		{
			Node	   *fc_this_default = (Node *) lfirst(fc_lc1);
			ColumnDef  *fc_def = (ColumnDef *) lfirst(fc_lc2);
			bool		fc_found_whole_row;

			/* 调整 Vars 以匹配新表的列编号 */
			fc_this_default = map_variable_attnos(fc_this_default,
											   1, 0,
											   fc_newattmap,
											   InvalidOid, &fc_found_whole_row);

			/*
			 * 目前我们必须拒绝整个行变量。如果我们知道新表的 rowtype OID，
			 * 我们可以转换它们，但那还没有分配。(一个变量只能出现在
			 * 生成表达式中，所以错误消息是正确的。)
			 */
			if (fc_found_whole_row)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("cannot convert whole-row table reference"),
						 errdetail("Generation expression for column \"%s\" contains a whole-row reference to table \"%s\".",
								   fc_def->colname,
								   RelationGetRelationName(fc_relation))));

			/*
			 * 如果我们已经有来自某个先前父级的默认值，请检查
			 * 它们是否相同。如果相同，则没有问题；如果不同，则标记该
			 * 列为有虚假默认值。下面，如果虚假默认值未被子模式覆盖，
			 * 我们会提出抱怨。
			 */
			Assert(fc_def->raw_default == NULL);
			if (fc_def->cooked_default == NULL)
				fc_def->cooked_default = fc_this_default;
			else if (!equal(fc_def->cooked_default, fc_this_default))
			{
				fc_def->cooked_default = &fc_bogus_marker;
				fc_have_bogus_defaults = true;
			}
		}

		/*
		 * 现在复制这个父级的 CHECK 约束，使用完成的 newattmap 映射调整 attnos。
		 * 同名约束如果可能合并，否则我们抛出错误。
		 */
		if (fc_constr && fc_constr->num_check > 0)
		{
			ConstrCheck *fc_check = fc_constr->check;
			int			fc_i;

			for (fc_i = 0; fc_i < fc_constr->num_check; fc_i++)
			{
				char	   *fc_name = fc_check[fc_i].ccname;
				Node	   *fc_expr;
				bool		fc_found_whole_row;

				/* 如果约束不可继承，则忽略 */
				if (fc_check[fc_i].ccnoinherit)
					continue;

				/* 调整 Vars 以匹配新表的列编号 */
				fc_expr = map_variable_attnos(stringToNode(fc_check[fc_i].ccbin),
										   1, 0,
										   fc_newattmap,
										   InvalidOid, &fc_found_whole_row);

				/*
				 * 目前我们必须拒绝整个行变量。如果我们知道新表的 rowtype OID，
				 * 我们可以转换它们，但那还没有分配。
				 */
				if (fc_found_whole_row)
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("cannot convert whole-row table reference"),
							 errdetail("Constraint \"%s\" contains a whole-row reference to table \"%s\".",
									   fc_name,
									   RelationGetRelationName(fc_relation))));

				/* 检查重复 */
				if (!fc_MergeCheckConstraint(fc_constraints, fc_name, fc_expr))
				{
					/* 不，这个是新的 */
					CookedConstraint *fc_cooked;

					fc_cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint));
					fc_cooked->contype = CONSTR_CHECK;
					fc_cooked->conoid = InvalidOid;	/* 直到创建完成 */
					fc_cooked->name = pstrdup(fc_name);
					fc_cooked->attnum = 0; /* 不用于约束 */
					fc_cooked->expr = fc_expr;
					fc_cooked->skip_validation = false;
					fc_cooked->is_local = false;
					fc_cooked->inhcount = 1;
					fc_cooked->is_no_inherit = false;
					fc_constraints = lappend(fc_constraints, fc_cooked);
				}
			}
		}

		free_attrmap(fc_newattmap);

		/*
		 * 关闭父级关系，但在 xact 提交之前保持对它的锁定。
		 * 这将防止其他人删除或修改父级
		 * 在子级提交之前。
		 */
		table_close(fc_relation, NoLock);
	}

	/*
	 * 如果我们没有继承属性，结果模式就是
	 * 显式声明的列。否则，我们需要将声明的
	 * 列合并到继承的模式列表中。虽然如果表是一个分区， 
	 * 我们从来没有任何显式声明的列。
	 */
	if (fc_inhSchema != NIL)
	{
		int			fc_schema_attno = 0;

		foreach(fc_entry, fc_schema)
		{
			ColumnDef  *fc_newdef = lfirst(fc_entry);
			char	   *fc_attributeName = fc_newdef->colname;
			int			fc_exist_attno;

			fc_schema_attno++;

			/*
			 * 它是否与某个先前继承的列冲突？
			 */
			fc_exist_attno = fc_findAttrByName(fc_attributeName, fc_inhSchema);
			if (fc_exist_attno > 0)
			{
				ColumnDef  *fc_def;
				Oid			fc_defTypeId,
							fc_newTypeId;
				int32		fc_deftypmod,
							fc_newtypmod;
				Oid			fc_defcollid,
							fc_newcollid;

				/*
				 * 分区只有一个父级，并且没有自己的列定义，因此冲突永远不会发生。
				 */
				Assert(!fc_is_partition);

				/*
				 * 是的，尝试合并这两个列定义。它们必须
				 * 具有相同的类型、typmod 和排序规则。
				 */
				if (fc_exist_attno == fc_schema_attno)
					ereport(NOTICE,
							(errmsg("merging column \"%s\" with inherited definition",
									fc_attributeName)));
				else
					ereport(NOTICE,
							(errmsg("moving and merging column \"%s\" with inherited definition", fc_attributeName),
							 errdetail("User-specified column moved to the position of the inherited column.")));
				fc_def = (ColumnDef *) list_nth(fc_inhSchema, fc_exist_attno - 1);
				typenameTypeIdAndMod(NULL, fc_def->typeName, &fc_defTypeId, &fc_deftypmod);
				typenameTypeIdAndMod(NULL, fc_newdef->typeName, &fc_newTypeId, &fc_newtypmod);
				if (fc_defTypeId != fc_newTypeId || fc_deftypmod != fc_newtypmod)
					ereport(ERROR,
							(errcode(ERRCODE_DATATYPE_MISMATCH),
							 errmsg("column \"%s\" has a type conflict",
									fc_attributeName),
							 errdetail("%s versus %s",
									   format_type_with_typemod(fc_defTypeId,
																fc_deftypmod),
									   format_type_with_typemod(fc_newTypeId,
																fc_newtypmod))));
				fc_defcollid = GetColumnDefCollation(NULL, fc_def, fc_defTypeId);
				fc_newcollid = GetColumnDefCollation(NULL, fc_newdef, fc_newTypeId);
				if (fc_defcollid != fc_newcollid)
					ereport(ERROR,
							(errcode(ERRCODE_COLLATION_MISMATCH),
							 errmsg("column \"%s\" has a collation conflict",
									fc_attributeName),
							 errdetail("\"%s\" versus \"%s\"",
									   get_collation_name(fc_defcollid),
									   get_collation_name(fc_newcollid))));

				/*
				 * 身份从不继承。新列可以具有身份定义，因此我们总是采用那个。
				 */
				fc_def->identity = fc_newdef->identity;

				/* 复制存储参数 */
				if (fc_def->storage == 0)
					fc_def->storage = fc_newdef->storage;
				else if (fc_newdef->storage != 0 && fc_def->storage != fc_newdef->storage)
					ereport(ERROR,
							(errcode(ERRCODE_DATATYPE_MISMATCH),
							 errmsg("column \"%s\" has a storage parameter conflict",
									fc_attributeName),
							 errdetail("%s versus %s",
									   fc_storage_name(fc_def->storage),
									   fc_storage_name(fc_newdef->storage))));

				/* 复制压缩参数 */
				if (fc_def->compression == NULL)
					fc_def->compression = fc_newdef->compression;
				else if (fc_newdef->compression != NULL)
				{
					if (strcmp(fc_def->compression, fc_newdef->compression) != 0)
						ereport(ERROR,
								(errcode(ERRCODE_DATATYPE_MISMATCH),
								 errmsg("column \"%s\" has a compression method conflict",
										fc_attributeName),
								 errdetail("%s versus %s", fc_def->compression, fc_newdef->compression)));
				}

				/* 将列标记为本地定义 */
				fc_def->is_local = true;
				/* 合并 NOT NULL 约束 = 或将其“与”在一起 */
				fc_def->is_not_null |= fc_newdef->is_not_null;

				/*
				 * 检查与生成列相关的冲突。
				 *
				 * 如果父列是生成的，则子列必须是未装饰的，并将成为生成列。（理论上我们可以允许子列定义指定完全相同的生成表达式，但那有点复杂，实现起来并不太有用。）我们还检查子列是否指定了默认值或身份，这符合 parse_util.c 中单列的规则。
				 */
				if (fc_def->generated)
				{
					if (fc_newdef->generated)
						ereport(ERROR,
								(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
								 errmsg("child column \"%s\" specifies generation expression",
										fc_def->colname),
								 errhint("Omit the generation expression in the definition of the child table column to inherit the generation expression from the parent table.")));
					if (fc_newdef->raw_default && !fc_newdef->generated)
						ereport(ERROR,
								(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
								 errmsg("column \"%s\" inherits from generated column but specifies default",
										fc_def->colname)));
					if (fc_newdef->identity)
						ereport(ERROR,
								(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
								 errmsg("column \"%s\" inherits from generated column but specifies identity",
										fc_def->colname)));
				}

				/*
				 * 如果父列不是生成的，则采用子列定义所说的内容。
				 */
				else
				{
					if (fc_newdef->generated)
						fc_def->generated = fc_newdef->generated;
				}

				/* 如果新定义具有默认值，则覆盖之前的默认值 */
				if (fc_newdef->raw_default != NULL)
				{
					fc_def->raw_default = fc_newdef->raw_default;
					fc_def->cooked_default = fc_newdef->cooked_default;
				}
			}
			else
			{
				/*
				 * 不，将新列附加到结果模式
				 */
				fc_inhSchema = lappend(fc_inhSchema, fc_newdef);
			}
		}

		fc_schema = fc_inhSchema;

		/*
		 * 检查在合并继承列后，我们是否超过了合法的列数。
		 */
		if (list_length(fc_schema) > MaxHeapAttributeNumber)
			ereport(ERROR,
					(errcode(ERRCODE_TOO_MANY_COLUMNS),
					 errmsg("tables can have at most %d columns",
							MaxHeapAttributeNumber)));
	}

	/*
	 * 现在我们有了分区的列定义列表，我们可以检查在列约束规范中引用的列是否实际存在。此外，我们将 NOT NULL 和默认值合并到每个相应的列定义中。
	 */
	if (fc_is_partition)
	{
		foreach(fc_entry, fc_saved_schema)
		{
			ColumnDef  *fc_restdef = lfirst(fc_entry);
			bool		fc_found = false;
			ListCell   *fc_l;

			foreach(fc_l, fc_schema)
			{
				ColumnDef  *fc_coldef = lfirst(fc_l);

				if (strcmp(fc_coldef->colname, fc_restdef->colname) == 0)
				{
					fc_found = true;
					fc_coldef->is_not_null |= fc_restdef->is_not_null;

					/*
					 * 用分区的本地定义（restdef->raw_default）覆盖该列的父级默认值（coldef->cooked_default），如果有的话。应当在本地定义中物理不可能获得烹饪后的默认值，或者在继承定义中获得原始默认值，但请确保它们为 null，以便未来能够应对。
					 */
					Assert(fc_restdef->cooked_default == NULL);
					Assert(fc_coldef->raw_default == NULL);
					if (fc_restdef->raw_default)
					{
						fc_coldef->raw_default = fc_restdef->raw_default;
						fc_coldef->cooked_default = NULL;
					}
				}
			}

			/* 对于不在父级中的列的约束进行抱怨 */
			if (!fc_found)
				ereport(ERROR,
						(errcode(ERRCODE_UNDEFINED_COLUMN),
						 errmsg("column \"%s\" does not exist",
								fc_restdef->colname)));
		}
	}

	/*
	 * 如果我们发现任何冲突的父级默认值，请检查确保它们已被子级覆盖。
	 */
	if (fc_have_bogus_defaults)
	{
		foreach(fc_entry, fc_schema)
		{
			ColumnDef  *fc_def = lfirst(fc_entry);

			if (fc_def->cooked_default == &fc_bogus_marker)
			{
				if (fc_def->generated)
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
							 errmsg("column \"%s\" inherits conflicting generation expressions",
									fc_def->colname)));
				else
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
							 errmsg("column \"%s\" inherits conflicting default values",
									fc_def->colname),
							 errhint("To resolve the conflict, specify a default explicitly.")));
			}
		}
	}

	*fc_supconstr = fc_constraints;
	return fc_schema;
}


/*
 * MergeCheckConstraint
 *		尝试将继承的 CHECK 约束与之前的合并
 *
 * 如果我们从多个父级继承具有相同名称的约束，则必须合并它们，或者如果它们没有相同的定义则抛出错误。
 *
 * constraints 是一个用于之前约束的 CookedConstraint 结构列表。
 *
 * 如果合并（约束是重复的），则返回 true；如果它具有迄今为止唯一的名称，则返回 false；如果存在冲突，则抛出错误。
 */
static bool fc_MergeCheckConstraint(List *fc_constraints, char *fc_name, Node *fc_expr)
{
	ListCell   *fc_lc;

	foreach(fc_lc, fc_constraints)
	{
		CookedConstraint *fc_ccon = (CookedConstraint *) lfirst(fc_lc);

		Assert(fc_ccon->contype == CONSTR_CHECK);

		/* 不匹配的名称从不冲突 */
		if (strcmp(fc_ccon->name, fc_name) != 0)
			continue;

		if (equal(fc_expr, fc_ccon->expr))
		{
			/* 可以合并 */
			fc_ccon->inhcount++;
			return true;
		}

		ereport(ERROR,
				(errcode(ERRCODE_DUPLICATE_OBJECT),
				 errmsg("check constraint name \"%s\" appears multiple times but with different expressions",
						fc_name)));
	}

	return false;
}


/*
 * StoreCatalogInheritance
 *		更新系统目录以提供正确的继承信息。
 *
 * supers 是新关系的直接祖先的 OID 列表。
 */
static void fc_StoreCatalogInheritance(Oid fc_relationId, List *fc_supers,
						bool fc_child_is_partition)
{
	Relation	fc_relation;
	int32		fc_seqNumber;
	ListCell   *fc_entry;

	/*
	 * 健康检查
	 */
	AssertArg(OidIsValid(fc_relationId));

	if (fc_supers == NIL)
		return;

	/*
	 * 仅使用直接祖先在 pg_inherits 中存储 INHERITS 信息。
	 * 同时输入对直接祖先的依赖，并确保它们被标记为 relhassubclass = true。
	 *
	 * （曾几何时，这里寻找直接和间接祖先，然后输入 pg_ipl。
	 * 由于该目录已不存在，因此不需要寻找间接祖先。）
	 */
	fc_relation = table_open(InheritsRelationId, RowExclusiveLock);

	fc_seqNumber = 1;
	foreach(fc_entry, fc_supers)
	{
		Oid			fc_parentOid = lfirst_oid(fc_entry);

		fc_StoreCatalogInheritance1(fc_relationId, fc_parentOid, fc_seqNumber, fc_relation,
								 fc_child_is_partition);
		fc_seqNumber++;
	}

	table_close(fc_relation, RowExclusiveLock);
}

/*
 * 创建目录项，显示 relationId 为 parentOid 的继承子。
 * inhRelation 是已经打开的 pg_inherits 目录。
 */
static void fc_StoreCatalogInheritance1(Oid fc_relationId, Oid fc_parentOid,
						 int32 fc_seqNumber, Relation fc_inhRelation,
						 bool fc_child_is_partition)
{
	ObjectAddress fc_childobject,
				fc_parentobject;

	/* 存储 pg_inherits 行 */
	StoreSingleInheritance(fc_relationId, fc_parentOid, fc_seqNumber);

	/*
	 * 也存储一个依赖
	 */
	fc_parentobject.classId = RelationRelationId;
	fc_parentobject.objectId = fc_parentOid;
	fc_parentobject.objectSubId = 0;
	fc_childobject.classId = RelationRelationId;
	fc_childobject.objectId = fc_relationId;
	fc_childobject.objectSubId = 0;

	recordDependencyOn(&fc_childobject, &fc_parentobject,
					   child_dependency_type(fc_child_is_partition));

	/*
	 * 此继承的创建后钩子。由于 object_access_hook
	 * 不接受多个对象标识符，我们通过 auxiliary_id 参数转发父关系的 oid。
	 */
	InvokeObjectPostAlterHookArg(InheritsRelationId,
								 fc_relationId, 0,
								 fc_parentOid, false);

	/*
	 * 标记父项为有子类。
	 */
	SetRelationHasSubclass(fc_parentOid, true);
}

/*
 * 查找具有给定名称的现有模式条目。
 *
 * 如果属性已存在于模式中，则返回索引（从 1 开始），
 * 如果不存在，则返回 0。
 */
static int fc_findAttrByName(const char *fc_attributeName, List *fc_schema)
{
	ListCell   *fc_s;
	int			fc_i = 1;

	foreach(fc_s, fc_schema)
	{
		ColumnDef  *fc_def = lfirst(fc_s);

		if (strcmp(fc_attributeName, fc_def->colname) == 0)
			return fc_i;

		fc_i++;
	}
	return 0;
}


/*
 * SetRelationHasSubclass
 *		设置关系在 pg_class 中的 relhassubclass 字段的值。
 *
 * 将该字段设置为 true 始终是安全的，因为所有 SQL 命令
 * 都已准备好看到 true，但随后发现没有子项。另一方面，命令
 * 通常假设如果这为 false 则没有子项。
 *
 * 调用者必须在事务结束之前保持任何自排他锁。如果
 * 新值为 false，则调用者必须在读取证明 false 值的证据之前
 * 已获得该锁。这样，如果另一个后端同时得出结论认为不需要更改元组（新值和旧值均为 true），则它会正确地等待。
 *
 * 注意：此操作的一个重要副作用是向所有后端（包括我）
 * 发送 SI 无效化消息，导致引用该关系的计划使用新的子项列表重建。
 * 即使发现在 pg_class 行中不需要任何更改，这也是必须发生的。
 */
void SetRelationHasSubclass(Oid fc_relationId, bool fc_relhassubclass)
{
	Relation	fc_relationRelation;
	HeapTuple	fc_tuple;
	Form_pg_class fc_classtuple;

	Assert(CheckRelationOidLockedByMe(fc_relationId,
									  ShareUpdateExclusiveLock, false) ||
		   CheckRelationOidLockedByMe(fc_relationId,
									  ShareRowExclusiveLock, true));

	/*
	 * 获取元组的可修改副本，修改它，更新 pg_class。
	 */
	fc_relationRelation = table_open(RelationRelationId, RowExclusiveLock);
	fc_tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(fc_relationId));
	if (!HeapTupleIsValid(fc_tuple))
		elog(ERROR, "cache lookup failed for relation %u", fc_relationId);
	fc_classtuple = (Form_pg_class) GETSTRUCT(fc_tuple);

	if (fc_classtuple->relhassubclass != fc_relhassubclass)
	{
		fc_classtuple->relhassubclass = fc_relhassubclass;
		CatalogTupleUpdate(fc_relationRelation, &fc_tuple->t_self, fc_tuple);
	}
	else
	{
		/* 无需更改元组，但仍强制重新构建 relcache */
		CacheInvalidateRelcacheByTuple(fc_tuple);
	}

	heap_freetuple(fc_tuple);
	table_close(fc_relationRelation, RowExclusiveLock);
}

/*
 * CheckRelationTableSpaceMove
 *		检查关系是否可以移动到新的表空间。
 *
 * 注：调用者必须对关系保持 AccessExclusiveLock。
 *
 * 如果关系可以移动到新的表空间，则返回 true；如果无法
 * 移动，则引发错误；如果移动没有影响，则返回 false。
 */
bool CheckRelationTableSpaceMove(Relation fc_rel, Oid fc_newTableSpaceId)
{
	Oid			fc_oldTableSpaceId;

	/*
	 * 如果表空间没有变化则无需工作。请注意，MyDatabaseTableSpace
	 * 存储为 0。
	 */
	fc_oldTableSpaceId = fc_rel->rd_rel->reltablespace;
	if (fc_newTableSpaceId == fc_oldTableSpaceId ||
		(fc_newTableSpaceId == MyDatabaseTableSpace && fc_oldTableSpaceId == 0))
		return false;

	/*
	 * 我们无法支持将映射关系移动到不同的表空间。
	 * （特别是这排除了所有共享目录。）
	 */
	if (RelationIsMapped(fc_rel))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot move system relation \"%s\"",
						RelationGetRelationName(fc_rel))));

	/* 不能将非共享的关系移动到 pg_global */
	if (fc_newTableSpaceId == GLOBALTABLESPACE_OID)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("only shared relations can be placed in pg_global tablespace")));

	/*
	 * 不允许移动其他后端的临时表……它们的本地
	 * 缓存管理器无法应对。
	 */
	if (RELATION_IS_OTHER_TEMP(fc_rel))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot move temporary tables of other sessions")));

	return true;
}

/*
 * SetRelationTableSpace
 *	设置 pg_class 条目的新 reltablespace 和 relfilenode。
 *
 * newTableSpaceId 是关系的新表空间，newRelFileNode 是
 * 其新的文件节点。如果 newRelFileNode 是 InvalidOid，
 * 则此字段不会被更新。
 *
 * 注意：调用者必须对关系持有 AccessExclusiveLock。
 *
 * 此例程的调用者最好先调用 CheckRelationTableSpaceMove()
 * 检查关系是否可以移动到这个新表空间，并负责通过
 * CommandCounterIncrement() 使更改可见。
 */
void SetRelationTableSpace(Relation fc_rel,
					  Oid fc_newTableSpaceId,
					  Oid fc_newRelFileNode)
{
	Relation	fc_pg_class;
	HeapTuple	fc_tuple;
	ItemPointerData fc_otid;
	Form_pg_class fc_rd_rel;
	Oid			fc_reloid = RelationGetRelid(fc_rel);

	Assert(CheckRelationTableSpaceMove(fc_rel, fc_newTableSpaceId));

	/* 获取关系的可修改 pg_class 行的副本。 */
	fc_pg_class = table_open(RelationRelationId, RowExclusiveLock);

	fc_tuple = SearchSysCacheLockedCopy1(RELOID, ObjectIdGetDatum(fc_reloid));
	if (!HeapTupleIsValid(fc_tuple))
		elog(ERROR, "cache lookup failed for relation %u", fc_reloid);
	fc_otid = fc_tuple->t_self;
	fc_rd_rel = (Form_pg_class) GETSTRUCT(fc_tuple);

	/* 更新 pg_class 行。 */
	fc_rd_rel->reltablespace = (fc_newTableSpaceId == MyDatabaseTableSpace) ?
		InvalidOid : fc_newTableSpaceId;
	if (OidIsValid(fc_newRelFileNode))
		fc_rd_rel->relfilenode = fc_newRelFileNode;
	CatalogTupleUpdate(fc_pg_class, &fc_otid, fc_tuple);
	UnlockTuple(fc_pg_class, &fc_otid, InplaceUpdateTupleLock);

	/*
	 * 记录对表空间的依赖。这仅对没有物理存储的关系
	 * 是必需的。
	 */
	if (!RELKIND_HAS_STORAGE(fc_rel->rd_rel->relkind))
		changeDependencyOnTablespace(RelationRelationId, fc_reloid,
									 fc_rd_rel->reltablespace);

	heap_freetuple(fc_tuple);
	table_close(fc_pg_class, RowExclusiveLock);
}

/*
 *		renameatt_check			- 属性重命名前的基本一致性检查
 */
static void fc_renameatt_check(Oid fc_myrelid, Form_pg_class fc_classform, bool fc_recursing)
{
	char		fc_relkind = fc_classform->relkind;

	if (fc_classform->reloftype && !fc_recursing)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("cannot rename column of typed table")));

	/*
	 * 重命名序列或 toasts 表的列实际上并不会
	 * 从系统的角度破坏任何东西，因为内部引用是通过 attnum
	 * 来实现的。但是，从某种意义上说，允许用户更改
	 * 硬编码在系统中的名称似乎不太合适，因此有以下
	 * 限制。
	 */
	if (fc_relkind != RELKIND_RELATION &&
		fc_relkind != RELKIND_VIEW &&
		fc_relkind != RELKIND_MATVIEW &&
		fc_relkind != RELKIND_COMPOSITE_TYPE &&
		fc_relkind != RELKIND_INDEX &&
		fc_relkind != RELKIND_PARTITIONED_INDEX &&
		fc_relkind != RELKIND_FOREIGN_TABLE &&
		fc_relkind != RELKIND_PARTITIONED_TABLE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("cannot rename columns of relation \"%s\"",
						NameStr(fc_classform->relname)),
				 errdetail_relkind_not_supported(fc_relkind)));

	/*
	 * 权限检查。只有类的所有者可以更改其模式。
	 */
	if (!pg_class_ownercheck(fc_myrelid, GetUserId()))
		aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(fc_myrelid)),
					   NameStr(fc_classform->relname));
	if (!allowSystemTableMods && IsSystemClass(fc_myrelid, fc_classform))
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("permission denied: \"%s\" is a system catalog",
						NameStr(fc_classform->relname))));
}

/*
 *		renameatt_internal		- renameatt 的工作程序
 *
 * 返回值是 'myrelid' 关系中的属性编号。
 */
static AttrNumber
fc_renameatt_internal(Oid fc_myrelid,
				   const char *fc_oldattname,
				   const char *fc_newattname,
				   bool fc_recurse,
				   bool fc_recursing,
				   int fc_expected_parents,
				   DropBehavior fc_behavior)
{
	Relation	fc_targetrelation;
	Relation	fc_attrelation;
	HeapTuple	fc_atttup;
	Form_pg_attribute fc_attform;
	AttrNumber	fc_attnum;

	/*
	 * 对目标表获取一个独占锁，直到事务结束之前
	 * 我们不会释放它。
	 */
	fc_targetrelation = relation_open(fc_myrelid, AccessExclusiveLock);
	fc_renameatt_check(fc_myrelid, RelationGetForm(fc_targetrelation), fc_recursing);

	/*
	 * 如果设置了 'recurse' 标志，那么我们应该在所有
	 * 继承自 'relname' 的类中（以及 'relname' 中）重命名此
	 * 属性。
	 *
	 * 任何权限或重复属性的问题将导致整个事务中止，
	 * 这正是我们所希望的—— 要么全部成功，要么全部失败。
	 */
	if (fc_recurse)
	{
		List	   *fc_child_oids,
				   *fc_child_numparents;
		ListCell   *fc_lo,
				   *fc_li;

		/*
		 * 我们需要每个子对象的父对象数量，以便递归
		 * 调用 renameatt() 可以确定是否有任何父对象
		 * 在正在处理的继承层次之外。
		 */
		fc_child_oids = find_all_inheritors(fc_myrelid, AccessExclusiveLock,
										 &fc_child_numparents);

		/*
		 * find_all_inheritors 执行继承层次的递归搜索，
		 * 所以我们要做的就是处理它返回的列表中的所有 relids。
		 */
		forboth(fc_lo, fc_child_oids, fc_li, fc_child_numparents)
		{
			Oid			fc_childrelid = lfirst_oid(fc_lo);
			int			fc_numparents = lfirst_int(fc_li);

			if (fc_childrelid == fc_myrelid)
				continue;
			/* 注意，我们不需要再次递归 */
			fc_renameatt_internal(fc_childrelid, fc_oldattname, fc_newattname, false, true, fc_numparents, fc_behavior);
		}
	}
	else
	{
		/*
		 * 如果我们被告知不进行递归，那么就应该没有任何子
		 * 表；否则重命名将使它们失去步调。
		 *
		 * expected_parents 仅在我们尚未进行递归时才会为 0。
		 */
		if (fc_expected_parents == 0 &&
			find_inheritance_children(fc_myrelid, NoLock) != NIL)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
					 errmsg("inherited column \"%s\" must be renamed in child tables too",
							fc_oldattname)));
	}

	/* 在复合类型的类型表中重命名属性 */
	if (fc_targetrelation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
	{
		List	   *fc_child_oids;
		ListCell   *fc_lo;

		fc_child_oids = fc_find_typed_table_dependencies(fc_targetrelation->rd_rel->reltype,
												   RelationGetRelationName(fc_targetrelation),
												   fc_behavior);

		foreach(fc_lo, fc_child_oids)
			fc_renameatt_internal(lfirst_oid(fc_lo), fc_oldattname, fc_newattname, true, true, 0, fc_behavior);
	}

	fc_attrelation = table_open(AttributeRelationId, RowExclusiveLock);

	fc_atttup = SearchSysCacheCopyAttName(fc_myrelid, fc_oldattname);
	if (!HeapTupleIsValid(fc_atttup))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_COLUMN),
				 errmsg("column \"%s\" does not exist",
						fc_oldattname)));
	fc_attform = (Form_pg_attribute) GETSTRUCT(fc_atttup);

	fc_attnum = fc_attform->attnum;
	if (fc_attnum <= 0)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot rename system column \"%s\"",
						fc_oldattname)));

	/*
	 * 如果属性是继承的，禁止重命名。如果这是对 renameatt() 的顶层调用，则 expected_parents 将为 0，因此这段代码的效果是如果该属性是继承的则禁止重命名。如果这是对 renameatt() 的递归调用，expected_parents 将是当前关系在正在处理的继承层次中的父级数量，因此我们将只在其他地方有额外父级时禁止重命名。
	 */
	if (fc_attform->attinhcount > fc_expected_parents)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
				 errmsg("cannot rename inherited column \"%s\"",
						fc_oldattname)));

	/* 新名称不应已经存在 */
	(void) fc_check_for_column_name_collision(fc_targetrelation, fc_newattname, false);

	/* 应用更新 */
	namestrcpy(&(fc_attform->attname), fc_newattname);

	CatalogTupleUpdate(fc_attrelation, &fc_atttup->t_self, fc_atttup);

	InvokeObjectPostAlterHook(RelationRelationId, fc_myrelid, fc_attnum);

	heap_freetuple(fc_atttup);

	table_close(fc_attrelation, RowExclusiveLock);

	relation_close(fc_targetrelation, NoLock); /* 关闭关系但保持锁定 */

	return fc_attnum;
}

/*
 * 在获取关系锁定之前执行权限和完整性检查。
 */
static void fc_RangeVarCallbackForRenameAttribute(const RangeVar *fc_rv, Oid fc_relid, Oid fc_oldrelid,
								   void *fc_arg)
{
	HeapTuple	fc_tuple;
	Form_pg_class fc_form;

	fc_tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(fc_relid));
	if (!HeapTupleIsValid(fc_tuple))
		return;					/* 并发删除 */
	fc_form = (Form_pg_class) GETSTRUCT(fc_tuple);
	fc_renameatt_check(fc_relid, fc_form, false);
	ReleaseSysCache(fc_tuple);
}

/*
 *		renameatt		- 修改关系中属性的名称
 *
 * 返回的 ObjectAddress 是重命名列的地址。
 */
ObjectAddress renameatt(RenameStmt *fc_stmt)
{
	Oid			fc_relid;
	AttrNumber	fc_attnum;
	ObjectAddress fc_address;

	/* 此处的锁级别应与 renameatt_internal 匹配 */
	fc_relid = RangeVarGetRelidExtended(fc_stmt->relation, AccessExclusiveLock,
									 fc_stmt->missing_ok ? RVR_MISSING_OK : 0,
									 fc_RangeVarCallbackForRenameAttribute,
									 NULL);

	if (!OidIsValid(fc_relid))
	{
		ereport(NOTICE,
				(errmsg("relation \"%s\" does not exist, skipping",
						fc_stmt->relation->relname)));
		return InvalidObjectAddress;
	}

	fc_attnum =
		fc_renameatt_internal(fc_relid,
						   fc_stmt->subname,	/* 旧属性名称 */
						   fc_stmt->newname,	/* 新属性名称 */
						   fc_stmt->relation->inh, /* 递归？ */
						   false,	/* 是否在递归？ */
						   0,	/* 预期的 inhcount */
						   fc_stmt->behavior);

	ObjectAddressSubSet(fc_address, RelationRelationId, fc_relid, fc_attnum);

	return fc_address;
}

/*
 * 与 renameatt_internal 的逻辑相同
 */
static ObjectAddress fc_rename_constraint_internal(Oid fc_myrelid,
						   Oid fc_mytypid,
						   const char *fc_oldconname,
						   const char *fc_newconname,
						   bool fc_recurse,
						   bool fc_recursing,
						   int fc_expected_parents)
{
	Relation	fc_targetrelation = NULL;
	Oid			fc_constraintOid;
	HeapTuple	fc_tuple;
	Form_pg_constraint fc_con;
	ObjectAddress fc_address;

	AssertArg(!fc_myrelid || !fc_mytypid);

	if (fc_mytypid)
	{
		fc_constraintOid = get_domain_constraint_oid(fc_mytypid, fc_oldconname, false);
	}
	else
	{
		fc_targetrelation = relation_open(fc_myrelid, AccessExclusiveLock);

		/*
		 * 不要告诉它我们是否在递归；我们允许在这里更改类型表
		 */
		fc_renameatt_check(fc_myrelid, RelationGetForm(fc_targetrelation), false);

		fc_constraintOid = get_relation_constraint_oid(fc_myrelid, fc_oldconname, false);
	}

	fc_tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fc_constraintOid));
	if (!HeapTupleIsValid(fc_tuple))
		elog(ERROR, "cache lookup failed for constraint %u",
			 fc_constraintOid);
	fc_con = (Form_pg_constraint) GETSTRUCT(fc_tuple);

	if (fc_myrelid && fc_con->contype == CONSTRAINT_CHECK && !fc_con->connoinherit)
	{
		if (fc_recurse)
		{
			List	   *fc_child_oids,
					   *fc_child_numparents;
			ListCell   *fc_lo,
					   *fc_li;

			fc_child_oids = find_all_inheritors(fc_myrelid, AccessExclusiveLock,
											 &fc_child_numparents);

			forboth(fc_lo, fc_child_oids, fc_li, fc_child_numparents)
			{
				Oid			fc_childrelid = lfirst_oid(fc_lo);
				int			fc_numparents = lfirst_int(fc_li);

				if (fc_childrelid == fc_myrelid)
					continue;

				fc_rename_constraint_internal(fc_childrelid, InvalidOid, fc_oldconname, fc_newconname, false, true, fc_numparents);
			}
		}
		else
		{
			if (fc_expected_parents == 0 &&
				find_inheritance_children(fc_myrelid, NoLock) != NIL)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
						 errmsg("inherited constraint \"%s\" must be renamed in child tables too",
								fc_oldconname)));
		}

		if (fc_con->coninhcount > fc_expected_parents)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
					 errmsg("cannot rename inherited constraint \"%s\"",
							fc_oldconname)));
	}

	if (fc_con->conindid
		&& (fc_con->contype == CONSTRAINT_PRIMARY
			|| fc_con->contype == CONSTRAINT_UNIQUE
			|| fc_con->contype == CONSTRAINT_EXCLUSION))
		/* 重命名索引；这也重命名约束 */
		RenameRelationInternal(fc_con->conindid, fc_newconname, false, true);
	else
		RenameConstraintById(fc_constraintOid, fc_newconname);

	ObjectAddressSet(fc_address, ConstraintRelationId, fc_constraintOid);

	ReleaseSysCache(fc_tuple);

	if (fc_targetrelation)
	{
		/*
		 * 使 relcache 无效，以便其他人可以看到新的约束名称。
		 */
		CacheInvalidateRelcache(fc_targetrelation);

		relation_close(fc_targetrelation, NoLock); /* 关闭关系但保持锁定 */
	}

	return fc_address;
}

ObjectAddress RenameConstraint(RenameStmt *fc_stmt)
{
	Oid			fc_relid = InvalidOid;
	Oid			fc_typid = InvalidOid;

	if (fc_stmt->renameType == OBJECT_DOMCONSTRAINT)
	{
		Relation	fc_rel;
		HeapTuple	fc_tup;

		fc_typid = typenameTypeId(NULL, makeTypeNameFromNameList(castNode(List, fc_stmt->object)));
		fc_rel = table_open(TypeRelationId, RowExclusiveLock);
		fc_tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(fc_typid));
		if (!HeapTupleIsValid(fc_tup))
			elog(ERROR, "cache lookup failed for type %u", fc_typid);
		checkDomainOwner(fc_tup);
		ReleaseSysCache(fc_tup);
		table_close(fc_rel, NoLock);
	}
	else
	{
		/* 此处的锁级别应与 rename_constraint_internal 匹配 */
		fc_relid = RangeVarGetRelidExtended(fc_stmt->relation, AccessExclusiveLock,
										 fc_stmt->missing_ok ? RVR_MISSING_OK : 0,
										 fc_RangeVarCallbackForRenameAttribute,
										 NULL);
		if (!OidIsValid(fc_relid))
		{
			ereport(NOTICE,
					(errmsg("relation \"%s\" does not exist, skipping",
							fc_stmt->relation->relname)));
			return InvalidObjectAddress;
		}
	}

	return
		fc_rename_constraint_internal(fc_relid, fc_typid,
								   fc_stmt->subname,
								   fc_stmt->newname,
								   (fc_stmt->relation &&
									fc_stmt->relation->inh),	/* 递归？ */
								   false,	/* 是否在递归？ */
								   0 /* 预期的 inhcount */ );
}

/*
 * 执行 ALTER TABLE/INDEX/SEQUENCE/VIEW/MATERIALIZED VIEW/FOREIGN TABLE
 * RENAME
 */
ObjectAddress RenameRelation(RenameStmt *fc_stmt)
{
	bool		fc_is_index_stmt = fc_stmt->renameType == OBJECT_INDEX;
	Oid			fc_relid;
	ObjectAddress fc_address;

	/*
	 * 在目标表、索引、序列、视图、物化视图或外部表上获取排他性锁定，我们不会在事务结束之前释放该锁定。
	 *
	 * 此处使用的锁级别应与 RenameRelationInternal 匹配，以避免锁升级。然而，由于 ALTER INDEX 可以与任何关系类型一起使用，我们在没有验证的情况下不能相信。
	 */
	for (;;)
	{
		LOCKMODE	fc_lockmode;
		char		fc_relkind;
		bool		fc_obj_is_index;

		fc_lockmode = fc_is_index_stmt ? ShareUpdateExclusiveLock : AccessExclusiveLock;

		fc_relid = RangeVarGetRelidExtended(fc_stmt->relation, fc_lockmode,
										 fc_stmt->missing_ok ? RVR_MISSING_OK : 0,
										 fc_RangeVarCallbackForAlterRelation,
										 (void *) fc_stmt);

		if (!OidIsValid(fc_relid))
		{
			ereport(NOTICE,
					(errmsg("relation \"%s\" does not exist, skipping",
							fc_stmt->relation->relname)));
			return InvalidObjectAddress;
		}

		/*
		 * 我们允许不匹配的语句和对象类型（例如，ALTER INDEX
		 * 重命名表），但我们可能使用了错误的锁级别。如果发生这种情况，请重试正确的锁级别。然而，如果我们已经用一个索引获得了 AccessExclusiveLock，则不会再做处理。
		 */
		fc_relkind = get_rel_relkind(fc_relid);
		fc_obj_is_index = (fc_relkind == RELKIND_INDEX ||
						fc_relkind == RELKIND_PARTITIONED_INDEX);
		if (fc_obj_is_index || fc_is_index_stmt == fc_obj_is_index)
			break;

		UnlockRelationOid(fc_relid, fc_lockmode);
		fc_is_index_stmt = fc_obj_is_index;
	}

	/* 执行操作 */
	RenameRelationInternal(fc_relid, fc_stmt->newname, false, fc_is_index_stmt);

	ObjectAddressSet(fc_address, RelationRelationId, fc_relid);

	return fc_address;
}

/*
 *		RenameRelationInternal - 更改关系的名称
 */
void RenameRelationInternal(Oid fc_myrelid, const char *fc_newrelname, bool fc_is_internal, bool fc_is_index)
{
	Relation	fc_targetrelation;
	Relation	fc_relrelation;	/* 对于关系关系 */
	ItemPointerData fc_otid;
	HeapTuple	fc_reltup;
	Form_pg_class fc_relform;
	Oid			fc_namespaceId;

	/*
	 * 在目标关系上获取锁定，我们不会在事务结束之前释放该锁定。需要至少一个自排他锁，以确保并发 DDL 在仍然看到旧版本时不会覆盖重命名。该锁还防止在并发会话中触发 relcache 重新加载，可能无法处理此信息的变化。对于索引，我们可以使用减少的锁定级别，因为 RelationReloadIndexInfo() 特殊处理索引。
	 */
	fc_targetrelation = relation_open(fc_myrelid, fc_is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock);
	fc_namespaceId = RelationGetNamespace(fc_targetrelation);

	/*
	 * 查找关系的 pg_class 元组，并确保 newrelname 没有在使用中。
	 */
	fc_relrelation = table_open(RelationRelationId, RowExclusiveLock);

	fc_reltup = SearchSysCacheLockedCopy1(RELOID, ObjectIdGetDatum(fc_myrelid));
	if (!HeapTupleIsValid(fc_reltup))	/* 不应该发生 */
		elog(ERROR, "cache lookup failed for relation %u", fc_myrelid);
	fc_otid = fc_reltup->t_self;
	fc_relform = (Form_pg_class) GETSTRUCT(fc_reltup);

	if (get_relname_relid(fc_newrelname, fc_namespaceId) != InvalidOid)
		ereport(ERROR,
				(errcode(ERRCODE_DUPLICATE_TABLE),
				 errmsg("relation \"%s\" already exists",
						fc_newrelname)));

	/*
	 * RenameRelation 小心不要相信调用者对
	 * 处理的关系类型的看法。我们不必担心这一点，但
	 * 也不要完全无视它。我们可以将索引处理为
	 * 不是索引，但不能反过来。
	 */
	Assert(!fc_is_index ||
		   fc_is_index == (fc_targetrelation->rd_rel->relkind == RELKIND_INDEX ||
						fc_targetrelation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX));

	/*
	 * 用新 relname 更新 pg_class 元组。 (在 reltup 上涂鸦是可以的
	 * 因为它是一个副本...)
	 */
	namestrcpy(&(fc_relform->relname), fc_newrelname);

	CatalogTupleUpdate(fc_relrelation, &fc_otid, fc_reltup);
	UnlockTuple(fc_relrelation, &fc_otid, InplaceUpdateTupleLock);

	InvokeObjectPostAlterHookArg(RelationRelationId, fc_myrelid, 0,
								 InvalidOid, fc_is_internal);

	heap_freetuple(fc_reltup);
	table_close(fc_relrelation, RowExclusiveLock);

	/*
	 * 如果有的话，也重命名关联的类型。
	 */
	if (OidIsValid(fc_targetrelation->rd_rel->reltype))
		RenameTypeInternal(fc_targetrelation->rd_rel->reltype,
						   fc_newrelname, fc_namespaceId);

	/*
	 * 如果有的话，也重命名关联的约束。
	 */
	if (fc_targetrelation->rd_rel->relkind == RELKIND_INDEX ||
		fc_targetrelation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
	{
		Oid			fc_constraintId = get_index_constraint(fc_myrelid);

		if (OidIsValid(fc_constraintId))
			RenameConstraintById(fc_constraintId, fc_newrelname);
	}

	/*
	 * 关闭关系，但保持锁定！
	 */
	relation_close(fc_targetrelation, NoLock);
}

/*
 *		ResetRelRewrite - 重置 relrewrite
 */
void ResetRelRewrite(Oid fc_myrelid)
{
	Relation	fc_relrelation;	/* 对于关系关系 */
	HeapTuple	fc_reltup;
	Form_pg_class fc_relform;

	/*
	 * 查找关系的 pg_class 元组。
	 */
	fc_relrelation = table_open(RelationRelationId, RowExclusiveLock);

	fc_reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(fc_myrelid));
	if (!HeapTupleIsValid(fc_reltup))	/* 不应该发生 */
		elog(ERROR, "cache lookup failed for relation %u", fc_myrelid);
	fc_relform = (Form_pg_class) GETSTRUCT(fc_reltup);

	/*
	 * 更新 pg_class 元组。
	 */
	fc_relform->relrewrite = InvalidOid;

	CatalogTupleUpdate(fc_relrelation, &fc_reltup->t_self, fc_reltup);

	heap_freetuple(fc_reltup);
	table_close(fc_relrelation, RowExclusiveLock);
}

/*
 * 当当前后端在目标表上有任何打开的引用时，禁止执行 ALTER TABLE
 * （及类似命令），除了调用命令刚获取的那个；这意味着有一个打开的游标或活动计划。
 * 我们需要这个检查，因为我们的锁不会保护我们不踩到自己的脚，只保护我们不踩到别人的脚！
 *
 * 对于 ALTER TABLE，已知会导致严重问题的唯一情况是 ALTER
 * COLUMN TYPE，一些更改显然是相当温和的，因此这可能
 * 可以放宽为仅对某些类型的更改返回错误。
 * 但是，允许这些操作的用例并不明显，因此我们
 * 目前不会对此做出过多努力。
 *
 * 如果有任何待处理的 AFTER 触发事件
 * 针对该关系，我们也会拒绝这些命令。这对于
 * ALTER TABLE 的重写变体显然是必要的，因为它们不保留元组 TID，因此待处理
 * 事件将试图获取错误的元组。在其他情况下这可能过于谨慎，
 * 但再次强调，从谨慎的角度出发似乎更好。
 *
 * REINDEX 以“rel”引用要重建的索引调用此; 在这里
 * 我们担心正在进行的索引扫描。触发事件的
 * 检查可以跳过，因为我们不会对父表造成损害。
 *
 * 语句名称（例如，“ALTER TABLE”）传递用于错误消息。
 */
void CheckTableNotInUse(Relation fc_rel, const char *fc_stmt)
{
	int			fc_expected_refcnt;

	fc_expected_refcnt = fc_rel->rd_isnailed ? 2 : 1;
	if (fc_rel->rd_refcnt != fc_expected_refcnt)
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_IN_USE),
		/* 翻译者：第一个 %s 是 SQL 命令，例如 ALTER TABLE */
				 errmsg("cannot %s \"%s\" because it is being used by active queries in this session",
						fc_stmt, RelationGetRelationName(fc_rel))));

	if (fc_rel->rd_rel->relkind != RELKIND_INDEX &&
		fc_rel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX &&
		AfterTriggerPendingOnRel(RelationGetRelid(fc_rel)))
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_IN_USE),
		/* 翻译者：第一个 %s 是 SQL 命令，例如 ALTER TABLE */
				 errmsg("cannot %s \"%s\" because it has pending trigger events",
						fc_stmt, RelationGetRelationName(fc_rel))));
}

/*
 * CheckAlterTableIsSafe
 *		验证允许在该关系上执行 ALTER TABLE 是否安全。
 *
 * 这由 CheckTableNotInUse() 以及检查该关系
 * 不是另一个会话的临时表组成。我们必须拆分出临时表
 * 检查，因为有些调用 CheckTableNotInUse() 的调用者不想
 * 要那样的检查，特别是 DROP TABLE。 (我们必须允许 DROP 否则无法清理
 * 孤立的临时模式。) 比较 truncate_check_activity()。
 */
static void fc_CheckAlterTableIsSafe(Relation fc_rel)
{
	/*
	 * 不允许对其他后端的临时表进行ALTER。它们的本地缓冲区
	 * 管理器将无法处理我们需要更改表内容的情况。
	 * 即使我们不这样做，也可能存在假设临时表
	 * 不会受到这种干扰的优化。
	 */
	if (RELATION_IS_OTHER_TEMP(fc_rel))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot alter temporary tables of other sessions")));

	/*
	 * 还要检查当前事务中对关系的活跃使用，
	 * 包括开放扫描和待处理的 AFTER 触发器事件。
	 */
	CheckTableNotInUse(fc_rel, "ALTER TABLE");
}

/*
 * AlterTableLookupRelation
 *		查找并锁定由ALTER TABLE
 *		语句指定的关系的OID。
 */
Oid AlterTableLookupRelation(AlterTableStmt *fc_stmt, LOCKMODE fc_lockmode)
{
	return RangeVarGetRelidExtended(fc_stmt->relation, fc_lockmode,
									fc_stmt->missing_ok ? RVR_MISSING_OK : 0,
									fc_RangeVarCallbackForAlterRelation,
									(void *) fc_stmt);
}

/*
 * AlterTable
 *		执行 ALTER TABLE，这可能是一个子命令的列表
 *
 * ALTER TABLE 的执行分为三个阶段：
 *		1. 检查子命令并进行前期转换检查。
 *		2. 验证和转换子命令，并更新系统目录。
 *		3. 扫描表以检查新的约束，并可选地将数据重新复制到新表中。
 * 第三阶段只有在一个或多个子命令需要时才会执行。这种设计的目的是允许对表模式进行多个独立的更新，仅需一次数据遍历。
 *
 * ATPrepCmd 执行第一阶段。为每个受影响的表创建一个“工作队列”条目（如果命令遍历表继承层次结构，可能会有多个受影响的表）。此外，我们还对子命令进行初步验证。由于早期的子命令可能会更改后续命令所看到的目录状态，因此在此阶段能够执行的操作有限。通常，此阶段会获取表锁，检查权限和 relkind，并递归查找子表。
 *
 * ATRewriteCatalogs 为每个受影响的表执行第二阶段。
 * 某些子命令需要在其他子命令之前执行，以避免不必要的冲突；例如，DROP COLUMN 应该在 ADD COLUMN 之前。因此第一阶段将子命令分为多个列表，每个列表对应第二阶段的一个逻辑“遍历”。
 *
 * ATRewriteTables 为需要的那些表执行第三阶段。
 *
 * 对于大多数子命令类型，第二阶段和第三阶段不进行显式递归，因为第一阶段已经完成了这项工作。然而，对于某些子命令类型，只能在第二阶段时确定如何递归；对于这些情况，第一阶段设置 cmd->recurse 标志（或者，在某些旧的代码中，将“递归”变体 XXX 的命令子类型更改为待清理状态）。
 *
 * 多亏了 MVCC 的魔力，任何地方的错误都会回滚整个操作；我们不需要做任何特别的清理工作。
 *
 * 调用者必须锁定关系，并使用适当的锁级别来满足请求的子命令，使用 AlterTableGetLockLevel(stmt->cmds) 或更高的级别。我们将锁级别向下传递，以便可以递归应用于继承的表。请注意，递归时我们希望的锁级别可能高于该特定子命令所需的级别。因此，我们传递整体锁要求，而不是在较低级别再进行评估。
 *
 * 调用者还提供一个“上下文”，将在我们需要执行诸如 CREATE INDEX 的子命令时传递回 utility.c。在其中的一些字段，例如 relid，这里也会使用。
 */
void AlterTable(AlterTableStmt *fc_stmt, LOCKMODE fc_lockmode,
		   AlterTableUtilityContext *fc_context)
{
	Relation	fc_rel;

	/* 调用者必须提供适当的锁。 */
	fc_rel = relation_open(fc_context->relid, NoLock);

	fc_CheckAlterTableIsSafe(fc_rel);

	fc_ATController(fc_stmt, fc_rel, fc_stmt->cmds, fc_stmt->relation->inh, fc_lockmode, fc_context);
}

/*
 * AlterTableInternal
 *
 * 使用OID指定目标的ALTER TABLE
 *
 * 如果关系已经打开，我们不会拒绝，因为调用者的一个或多个层可能已经打开它。这意味着对于可能破坏现有查询计划的更改，使用这个入口点是不安全的。假设它不用于这种情况，我们也不必拒绝待定的AFTER触发器。
 *
 * 此外，由于我们没有AlterTableUtilityContext，这不能用于任何需要解析转换的子命令类型或可能生成必须传递给ProcessUtility的子命令。
 */
void AlterTableInternal(Oid fc_relid, List *fc_cmds, bool fc_recurse)
{
	Relation	fc_rel;
	LOCKMODE	fc_lockmode = AlterTableGetLockLevel(fc_cmds);

	fc_rel = relation_open(fc_relid, fc_lockmode);

	EventTriggerAlterTableRelid(fc_relid);

	fc_ATController(NULL, fc_rel, fc_cmds, fc_recurse, fc_lockmode, NULL);
}

/*
 * AlterTableGetLockLevel
 *
 * 为提供的子命令列表设置所需的整体锁级别。
 * 根据AlterTable()的需要设置此策略，参见那里关于整体解释的注释。
 *
 * 函数在解析之前和之后被调用，因此它每次调用时必须给出相同的答案。一些子命令被转化为其他子命令类型，因此转换绝不能降低锁级别。所有转换在下面说明。
 *
 * 由于这是在我们锁定表之前调用的，因此我们不能使用表元数据来影响我们获取的锁类型。
 *
 * 子命令函数中不应硬编码锁模式。所有ALTER TABLE的锁模式决策仅在此进行。唯一的例外是ALTER TABLE RENAME，它被视为不同的语句类型T_RenameStmt，不会通过这段代码，并且不能与这里给出的任何子命令结合使用。
 *
 * 请注意，热备份仅在主节点上了解AccessExclusiveLocks，因此可能影响在备用节点上运行的SELECT的任何更改需要使用AccessExclusiveLocks，即使您认为较低的锁也能满足需求，除非您对此也有解决方案。
 *
 * 还要注意，pg_dump仅使用AccessShareLock，这意味着任何获取低于AccessExclusiveLock锁的操作都可以在pg_dump运行时更改对象定义。务必小心检查pg_dump使用MVCC快照而不是系统缓存查找得到的适当数据，否则我们可能会得到一个不一致的转储，无法恢复。
 */
LOCKMODE
AlterTableGetLockLevel(List *fc_cmds)
{
	/*
	 * 这仅在我们使用MVCC快照读取目录表时有效。
	 */
	ListCell   *fc_lcmd;
	LOCKMODE	fc_lockmode = ShareUpdateExclusiveLock;

	foreach(fc_lcmd, fc_cmds)
	{
		AlterTableCmd *fc_cmd = (AlterTableCmd *) lfirst(fc_lcmd);
		LOCKMODE	fc_cmd_lockmode = AccessExclusiveLock; /* 编译器的默认值 */

		switch (fc_cmd->subtype)
		{
				/*
				 * 这些子命令重写堆，因此需要完全锁定。
				 */
			case AT_AddColumn:	/* 可能重写堆，在某些情况下可见
								 * 给SELECT */
			case AT_SetAccessMethod:	/* 必须重写堆 */
			case AT_SetTableSpace:	/* 必须重写堆 */
			case AT_AlterColumnType:	/* 必须重写堆 */
				fc_cmd_lockmode = AccessExclusiveLock;
				break;

				/*
				 * 这些子命令可能需要添加toast表。如果
				 * 我们向当前被扫描的表添加toast表，可能会错过并发
				 * 插入事务对新toast表添加的数据。
				 */
			case AT_SetStorage: /* 可能添加toast表，见
								 * ATRewriteCatalogs() */
				fc_cmd_lockmode = AccessExclusiveLock;
				break;

				/*
				 * 移除约束可能会影响假设约束成立的SELECTs。
				 * 另见CloneFkReferenced。
				 */
			case AT_DropConstraint: /* 如同DROP INDEX */
			case AT_DropNotNull:	/* 可能会改变一些SQL计划 */
				fc_cmd_lockmode = AccessExclusiveLock;
				break;

				/*
				 * 可能对并发SELECTs可见的子命令
				 */
			case AT_DropColumn: /* 改变SELECT的可见性 */
			case AT_AddColumnToView:	/* 创建视图 */
			case AT_DropOids:	/* 用于等同于DropColumn */
			case AT_EnableAlwaysRule:	/* 可能改变SELECT规则 */
			case AT_EnableReplicaRule:	/* 可能改变SELECT规则 */
			case AT_EnableRule: /* 可能改变SELECT规则 */
			case AT_DisableRule:	/* 可能改变SELECT规则 */
				fc_cmd_lockmode = AccessExclusiveLock;
				break;

				/*
				 * 更改所有者可能会移除隐式SELECT权限
				 */
			case AT_ChangeOwner:	/* 改变SELECT的可见性 */
				fc_cmd_lockmode = AccessExclusiveLock;
				break;

				/*
				 * 更改外部表选项可能会影响优化。
				 */
			case AT_GenericOptions:
			case AT_AlterColumnGenericOptions:
				fc_cmd_lockmode = AccessExclusiveLock;
				break;

				/*
				 * 这些子命令仅影响写操作。
				 */
			case AT_EnableTrig:
			case AT_EnableAlwaysTrig:
			case AT_EnableReplicaTrig:
			case AT_EnableTrigAll:
			case AT_EnableTrigUser:
			case AT_DisableTrig:
			case AT_DisableTrigAll:
			case AT_DisableTrigUser:
				fc_cmd_lockmode = ShareRowExclusiveLock;
				break;

				/*
				 * 这些子命令仅影响写操作。理论上，它们可以是ShareRowExclusiveLock。
				 */
			case AT_ColumnDefault:
			case AT_CookedColumnDefault:
			case AT_AlterConstraint:
			case AT_AddIndex:	/* 来自ADD CONSTRAINT */
			case AT_AddIndexConstraint:
			case AT_ReplicaIdentity:
			case AT_SetNotNull:
			case AT_EnableRowSecurity:
			case AT_DisableRowSecurity:
			case AT_ForceRowSecurity:
			case AT_NoForceRowSecurity:
			case AT_AddIdentity:
			case AT_DropIdentity:
			case AT_SetIdentity:
			case AT_DropExpression:
			case AT_SetCompression:
				fc_cmd_lockmode = AccessExclusiveLock;
				break;

			case AT_AddConstraint:
			case AT_AddConstraintRecurse:	/* 变为AT_AddConstraint */
			case AT_ReAddConstraint:	/* 变为AT_AddConstraint */
			case AT_ReAddDomainConstraint:	/* 变为AT_AddConstraint */
				if (IsA(fc_cmd->def, Constraint))
				{
					Constraint *fc_con = (Constraint *) fc_cmd->def;

					switch (fc_con->contype)
					{
						case CONSTR_EXCLUSION:
						case CONSTR_PRIMARY:
						case CONSTR_UNIQUE:

							/*
							 * 情形与CREATE INDEX基本相同。如果我们能找出如何允许并发目录更新，
							 * 我们可以将锁强度降低到ShareLock。可能会设置为ShareRowExclusiveLock，
							 * 但需要进一步分析。
							 */
							fc_cmd_lockmode = AccessExclusiveLock;
							break;
						case CONSTR_FOREIGN:

							/*
							 * 在添加外键时，我们会向两个表添加触发器，
							 * 因此锁级别必须至少与CREATE TRIGGER一样强。
							 */
							fc_cmd_lockmode = ShareRowExclusiveLock;
							break;

						default:
							fc_cmd_lockmode = AccessExclusiveLock;
					}
				}
				break;

				/*
				 * 这些子命令影响继承行为。在我们之前开始的查询将继续看到旧的继承
				 * 行为，而在我们提交后开始的查询将看到新的行为。然而，在我们连接
				 * 子表的过程中没有必要阻止对它的读或写。更改TupDesc可能会成为问题，
				 * 因此保持最高的锁级别。
				 */
			case AT_AddInherit:
			case AT_DropInherit:
				fc_cmd_lockmode = AccessExclusiveLock;
				break;

				/*
				 * 这些子命令影响隐式行类型转换。它们对查询的影响类似于CREATE/DROP CAST。
				 * 不提供因这种更改而使解析树失效的机制，因此我们将其保持在AccessExclusiveLock。
				 */
			case AT_AddOf:
			case AT_DropOf:
				fc_cmd_lockmode = AccessExclusiveLock;
				break;

				/*
				 * 仅由CREATE OR REPLACE VIEW使用，该操作必须与当前
				 * 使用视图的SELECTs发生冲突。
				 */
			case AT_ReplaceRelOptions:
				fc_cmd_lockmode = AccessExclusiveLock;
				break;

				/*
				 * 这些子命令影响性能和维护的一般策略，但不会改变
				 * 正常数据读取和写入的语义结果。将ALTER TABLE延迟到当前活动写入
				 * 后只会延迟新策略开始生效的时刻，因此没有
				 * 等待的好处。在这种情况下，适用最低限制：
				 * 我们目前不允许并发目录更新。
				 */
			case AT_SetStatistics:	/* 在getTableAttrs()中使用MVCC */
			case AT_ClusterOn:	/* 在getIndexes()中使用MVCC */
			case AT_DropCluster:	/* 在getIndexes()中使用MVCC */
			case AT_SetOptions: /* 在getTableAttrs()中使用MVCC */
			case AT_ResetOptions:	/* 在getTableAttrs()中使用MVCC */
				fc_cmd_lockmode = ShareUpdateExclusiveLock;
				break;

			case AT_SetLogged:
			case AT_SetUnLogged:
				fc_cmd_lockmode = AccessExclusiveLock;
				break;

			case AT_ValidateConstraint: /* 在 getConstraints() 中使用 MVCC */
				fc_cmd_lockmode = ShareUpdateExclusiveLock;
				break;

				/*
				 * 关系选项比表面看起来更复杂。选项
				 * 在这里为表、视图和索引设置；出于历史
				 * 原因，这些都可以与 ALTER TABLE 一起使用，因此我们无法
				 * 使用基本语法来区分它们。
				 */
			case AT_SetRelOptions:	/* 在 getIndexes() 和
									 * getTables() 中使用 MVCC */
			case AT_ResetRelOptions:	/* 在 getIndexes() 和
										 * getTables() 中使用 MVCC */
				fc_cmd_lockmode = AlterTableGetRelOptionsLockLevel((List *) fc_cmd->def);
				break;

			case AT_AttachPartition:
				fc_cmd_lockmode = ShareUpdateExclusiveLock;
				break;

			case AT_DetachPartition:
				if (((PartitionCmd *) fc_cmd->def)->concurrent)
					fc_cmd_lockmode = ShareUpdateExclusiveLock;
				else
					fc_cmd_lockmode = AccessExclusiveLock;
				break;

			case AT_DetachPartitionFinalize:
				fc_cmd_lockmode = ShareUpdateExclusiveLock;
				break;

			case AT_CheckNotNull:

				/*
				 * 这只检查表的模式；但锁必须足够强大以防止并发的 DROP NOT NULL。
				 */
				fc_cmd_lockmode = AccessShareLock;
				break;

			default:			/* 哦，糟糕 */
				elog(ERROR, "unrecognized alter table type: %d",
					 (int) fc_cmd->subtype);
				break;
		}

		/*
		 * 从任何子命令中获取最大锁模式
		 */
		if (fc_cmd_lockmode > fc_lockmode)
			fc_lockmode = fc_cmd_lockmode;
	}

	return fc_lockmode;
}

/*
 * ATController 提供对各个阶段的顶层控制。
 *
 * parsetree 被传入，以便在请求时可以将其传递给事件触发器。
 */
static void fc_ATController(AlterTableStmt *fc_parsetree,
			 Relation fc_rel, List *fc_cmds, bool fc_recurse, LOCKMODE fc_lockmode,
			 AlterTableUtilityContext *fc_context)
{
	List	   *fc_wqueue = NIL;
	ListCell   *fc_lcmd;

	/* 阶段 1：命令的初步检查，创建工作队列 */
	foreach(fc_lcmd, fc_cmds)
	{
		AlterTableCmd *fc_cmd = (AlterTableCmd *) lfirst(fc_lcmd);

		fc_ATPrepCmd(&fc_wqueue, fc_rel, fc_cmd, fc_recurse, false, fc_lockmode, fc_context);
	}

	/* 关闭关系，但在提交之前保持锁定 */
	relation_close(fc_rel, NoLock);

	/* 阶段 2：更新系统目录 */
	fc_ATRewriteCatalogs(&fc_wqueue, fc_lockmode, fc_context);

	/* 阶段 3：根据需要扫描/重写表，并运行 afterStmts */
	fc_ATRewriteTables(fc_parsetree, &fc_wqueue, fc_lockmode, fc_context);
}

/*
 * ATPrepCmd
 *
 * ALTER TABLE 阶段 1 操作的交通管理，包括简单
 * 递归和权限检查。
 *
 * 调用者必须已经在关系上获取适当的锁类型。
 * 此锁应在提交之前保持。
 */
static void fc_ATPrepCmd(List **fc_wqueue, Relation fc_rel, AlterTableCmd *fc_cmd,
		  bool fc_recurse, bool fc_recursing, LOCKMODE fc_lockmode,
		  AlterTableUtilityContext *fc_context)
{
	AlteredTableInfo *fc_tab;
	int			fc_pass = AT_PASS_UNSET;

	/* 查找或创建该表的工作队列条目 */
	fc_tab = fc_ATGetQueueEntry(fc_wqueue, fc_rel);

	/*
	 * 禁止对待处理分区进行除 ALTER TABLE DETACH FINALIZE 之外的任何 ALTER TABLE 操作。
	 */
	if (fc_rel->rd_rel->relispartition &&
		fc_cmd->subtype != AT_DetachPartitionFinalize &&
		PartitionHasPendingDetach(RelationGetRelid(fc_rel)))
		ereport(ERROR,
				errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				errmsg("cannot alter partition \"%s\" with an incomplete detach",
					   RelationGetRelationName(fc_rel)),
				errhint("Use ALTER TABLE ... DETACH PARTITION ... FINALIZE to complete the pending detach operation."));

	/*
	 * 为每个表复制原始子命令，以便我们可以在其上进行修改。
	 * 这可以避免在不同的子表需要进行
	 * 不同的解析转换时发生冲突（例如，相同的列在不同的子表中可能有
	 * 不同的列号）。
	 */
	fc_cmd = copyObject(fc_cmd);

	/*
	 * 进行权限和 relkind 检查，必要时递归到子表，以及任何其他
	 * 阶段 1 所需的处理。（但要注意
	 * 添加任何查看表细节的处理，这些细节可能会被其他
	 * 子命令更改。在某些情况下，我们拒绝多个子命令
	 * 这些子命令可能会试图以相反的方式更改相同的状态。）
	 */
	switch (fc_cmd->subtype)
	{
		case AT_AddColumn:		/* ADD COLUMN */
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel,
								ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
			fc_ATPrepAddColumn(fc_wqueue, fc_rel, fc_recurse, fc_recursing, false, fc_cmd,
							fc_lockmode, fc_context);
			/* 在执行阶段发生递归 */
			fc_pass = AT_PASS_ADD_COL;
			break;
		case AT_AddColumnToView:	/* 通过 CREATE OR REPLACE VIEW 添加列 */
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_VIEW);
			fc_ATPrepAddColumn(fc_wqueue, fc_rel, fc_recurse, fc_recursing, true, fc_cmd,
							fc_lockmode, fc_context);
			/* 在执行阶段发生递归 */
			fc_pass = AT_PASS_ADD_COL;
			break;
		case AT_ColumnDefault:	/* 修改列默认值 */

			/*
			 * 我们允许在视图上使用默认值，以便对视图的 INSERT 可以具有
			 * 类似默认的行为。这是有效的，因为重写器
			 * 在扩展规则之前将默认值替换到 INSERT 中。
			 */
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
			fc_ATSimpleRecursion(fc_wqueue, fc_rel, fc_cmd, fc_recurse, fc_lockmode, fc_context);
			/* 无需特定命令的准备 */
			fc_pass = fc_cmd->def ? AT_PASS_ADD_OTHERCONSTR : AT_PASS_DROP;
			break;
		case AT_CookedColumnDefault:	/* 添加一个预处理的默认值 */
			/* 目前仅在 CREATE TABLE 中使用 */
			/* （因此权限检查实际上不是必要的） */
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_TABLE | ATT_FOREIGN_TABLE);
			/* 此命令永远不会递归 */
			fc_pass = AT_PASS_ADD_OTHERCONSTR;
			break;
		case AT_AddIdentity:
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
			/* 此命令永远不会递归 */
			fc_pass = AT_PASS_ADD_OTHERCONSTR;
			break;
		case AT_SetIdentity:
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
			/* 此命令永远不会递归 */
			/* 这应该在 AddIdentity 之后运行，因此在 MISC 处理中执行 */
			fc_pass = AT_PASS_MISC;
			break;
		case AT_DropIdentity:
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
			/* 此命令永远不会递归 */
			fc_pass = AT_PASS_DROP;
			break;
		case AT_DropNotNull:	/* ALTER COLUMN DROP NOT NULL */
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_TABLE | ATT_FOREIGN_TABLE);
			fc_ATPrepDropNotNull(fc_rel, fc_recurse, fc_recursing);
			fc_ATSimpleRecursion(fc_wqueue, fc_rel, fc_cmd, fc_recurse, fc_lockmode, fc_context);
			fc_pass = AT_PASS_DROP;
			break;
		case AT_SetNotNull:		/* ALTER COLUMN SET NOT NULL */
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_TABLE | ATT_FOREIGN_TABLE);
			/* 需要命令特定的递归决策 */
			fc_ATPrepSetNotNull(fc_wqueue, fc_rel, fc_cmd, fc_recurse, fc_recursing,
							 fc_lockmode, fc_context);
			fc_pass = AT_PASS_COL_ATTRS;
			break;
		case AT_CheckNotNull:	/* 检查列是否已标记为 NOT NULL */
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_TABLE | ATT_FOREIGN_TABLE);
			fc_ATSimpleRecursion(fc_wqueue, fc_rel, fc_cmd, fc_recurse, fc_lockmode, fc_context);
			/* 无需特定命令的准备 */
			fc_pass = AT_PASS_COL_ATTRS;
			break;
		case AT_DropExpression: /* ALTER COLUMN DROP EXPRESSION */
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_TABLE | ATT_FOREIGN_TABLE);
			fc_ATSimpleRecursion(fc_wqueue, fc_rel, fc_cmd, fc_recurse, fc_lockmode, fc_context);
			fc_ATPrepDropExpression(fc_rel, fc_cmd, fc_recurse, fc_recursing, fc_lockmode);
			fc_pass = AT_PASS_DROP;
			break;
		case AT_SetStatistics:	/* ALTER COLUMN SET STATISTICS */
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_TABLE | ATT_MATVIEW | ATT_INDEX | ATT_PARTITIONED_INDEX | ATT_FOREIGN_TABLE);
			fc_ATSimpleRecursion(fc_wqueue, fc_rel, fc_cmd, fc_recurse, fc_lockmode, fc_context);
			/* 无需特定命令的准备 */
			fc_pass = AT_PASS_MISC;
			break;
		case AT_SetOptions:		/* ALTER COLUMN SET ( options ) */
		case AT_ResetOptions:	/* ALTER COLUMN RESET ( options ) */
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_TABLE | ATT_MATVIEW | ATT_FOREIGN_TABLE);
			/* 此命令永远不会递归 */
			fc_pass = AT_PASS_MISC;
			break;
		case AT_SetStorage:		/* ALTER COLUMN SET STORAGE */
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_TABLE | ATT_MATVIEW | ATT_FOREIGN_TABLE);
			fc_ATSimpleRecursion(fc_wqueue, fc_rel, fc_cmd, fc_recurse, fc_lockmode, fc_context);
			/* 无需特定命令的准备 */
			fc_pass = AT_PASS_MISC;
			break;
		case AT_SetCompression: /* ALTER COLUMN SET COMPRESSION */
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_TABLE | ATT_MATVIEW);
			/* 此命令永远不会递归 */
			/* 无需特定命令的准备 */
			fc_pass = AT_PASS_MISC;
			break;
		case AT_DropColumn:		/* DROP COLUMN */
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel,
								ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
			fc_ATPrepDropColumn(fc_wqueue, fc_rel, fc_recurse, fc_recursing, fc_cmd,
							 fc_lockmode, fc_context);
			/* 在执行阶段发生递归 */
			fc_pass = AT_PASS_DROP;
			break;
		case AT_AddIndex:		/* ADD INDEX */
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_TABLE);
			/* 此命令永远不会递归 */
			/* 无需特定命令的准备 */
			fc_pass = AT_PASS_ADD_INDEX;
			break;
		case AT_AddConstraint:	/* ADD CONSTRAINT */
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_TABLE | ATT_FOREIGN_TABLE);
			/* 在执行阶段发生递归 */
			/* 除了保存递归标志之外，不需要命令特定的准备 */
			if (fc_recurse)
				fc_cmd->subtype = AT_AddConstraintRecurse;
			fc_pass = AT_PASS_ADD_CONSTR;
			break;
		case AT_AddIndexConstraint: /* ADD CONSTRAINT USING INDEX */
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_TABLE);
			/* 此命令永远不会递归 */
			/* 无需特定命令的准备 */
			fc_pass = AT_PASS_ADD_INDEXCONSTR;
			break;
		case AT_DropConstraint: /* DROP CONSTRAINT */
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_TABLE | ATT_FOREIGN_TABLE);
			fc_ATCheckPartitionsNotInUse(fc_rel, fc_lockmode);
			/* 其他递归发生在执行阶段 */
			/* 除了保存递归标志之外，不需要命令特定的准备 */
			if (fc_recurse)
				fc_cmd->subtype = AT_DropConstraintRecurse;
			fc_pass = AT_PASS_DROP;
			break;
		case AT_AlterColumnType:	/* ALTER COLUMN TYPE */
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel,
								ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
			/* 查看关于 ATPrepAlterColumnType 的评论 */
			fc_cmd = fc_ATParseTransformCmd(fc_wqueue, fc_tab, fc_rel, fc_cmd, fc_recurse, fc_lockmode,
									  AT_PASS_UNSET, fc_context);
			Assert(fc_cmd != NULL);
			/* 执行自己的递归 */
			fc_ATPrepAlterColumnType(fc_wqueue, fc_tab, fc_rel, fc_recurse, fc_recursing, fc_cmd,
								  fc_lockmode, fc_context);
			fc_pass = AT_PASS_ALTER_TYPE;
			break;
		case AT_AlterColumnGenericOptions:
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_FOREIGN_TABLE);
			/* 此命令永远不会递归 */
			/* 无需特定命令的准备 */
			fc_pass = AT_PASS_MISC;
			break;
		case AT_ChangeOwner:	/* ALTER OWNER */
			/* 此命令永远不会递归 */
			/* 无需特定命令的准备 */
			fc_pass = AT_PASS_MISC;
			break;
		case AT_ClusterOn:		/* CLUSTER ON */
		case AT_DropCluster:	/* SET WITHOUT CLUSTER */
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_TABLE | ATT_MATVIEW);
			/* 这些命令不会递归 */
			/* 无需特定命令的准备 */
			fc_pass = AT_PASS_MISC;
			break;
		case AT_SetLogged:		/* SET LOGGED */
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_TABLE | ATT_SEQUENCE);
			if (fc_tab->chgPersistence)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("cannot change persistence setting twice")));
			fc_tab->chgPersistence = fc_ATPrepChangePersistence(fc_rel, true);
			/* 如果必要，强制重写；请参见 ATRewriteTables 中的注释 */
			if (fc_tab->chgPersistence)
			{
				fc_tab->rewrite |= AT_REWRITE_ALTER_PERSISTENCE;
				fc_tab->newrelpersistence = RELPERSISTENCE_PERMANENT;
			}
			fc_pass = AT_PASS_MISC;
			break;
		case AT_SetUnLogged:	/* SET UNLOGGED */
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_TABLE | ATT_SEQUENCE);
			if (fc_tab->chgPersistence)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("cannot change persistence setting twice")));
			fc_tab->chgPersistence = fc_ATPrepChangePersistence(fc_rel, false);
			/* 如果必要，强制重写；请参见 ATRewriteTables 中的注释 */
			if (fc_tab->chgPersistence)
			{
				fc_tab->rewrite |= AT_REWRITE_ALTER_PERSISTENCE;
				fc_tab->newrelpersistence = RELPERSISTENCE_UNLOGGED;
			}
			fc_pass = AT_PASS_MISC;
			break;
		case AT_DropOids:		/* SET WITHOUT OIDS */
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_TABLE | ATT_FOREIGN_TABLE);
			fc_pass = AT_PASS_DROP;
			break;
		case AT_SetAccessMethod:	/* SET ACCESS METHOD */
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_TABLE | ATT_MATVIEW);

			/* 分区表没有访问方法 */
			if (fc_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
				ereport(ERROR,
						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
						 errmsg("cannot change access method of a partitioned table")));

			/* 检查是否已经请求了另一个访问方法的更改 */
			if (OidIsValid(fc_tab->newAccessMethod))
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("cannot have multiple SET ACCESS METHOD subcommands")));

			fc_ATPrepSetAccessMethod(fc_tab, fc_rel, fc_cmd->name);
			fc_pass = AT_PASS_MISC;	/* 无所谓；在第 2 阶段没有工作 */
			break;
		case AT_SetTableSpace:	/* SET TABLESPACE */
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_TABLE | ATT_MATVIEW | ATT_INDEX |
								ATT_PARTITIONED_INDEX);
			/* 此命令永远不会递归 */
			fc_ATPrepSetTableSpace(fc_tab, fc_rel, fc_cmd->name, fc_lockmode);
			fc_pass = AT_PASS_MISC;	/* 实际上并不重要 */
			break;
		case AT_SetRelOptions:	/* SET (...) */
		case AT_ResetRelOptions:	/* RESET (...) */
		case AT_ReplaceRelOptions:	/* 先重置所有，然后仅设置这些 */
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_TABLE | ATT_VIEW | ATT_MATVIEW | ATT_INDEX);
			/* 此命令永远不会递归 */
			/* 无需特定命令的准备 */
			fc_pass = AT_PASS_MISC;
			break;
		case AT_AddInherit:		/* INHERIT */
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_TABLE | ATT_FOREIGN_TABLE);
			/* 此命令永远不会递归 */
			fc_ATPrepAddInherit(fc_rel);
			fc_pass = AT_PASS_MISC;
			break;
		case AT_DropInherit:	/* NO INHERIT */
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_TABLE | ATT_FOREIGN_TABLE);
			/* 此命令永远不会递归 */
			/* 无需特定命令的准备 */
			fc_pass = AT_PASS_MISC;
			break;
		case AT_AlterConstraint:	/* ALTER CONSTRAINT */
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_TABLE);
			/* 在执行阶段发生递归 */
			fc_pass = AT_PASS_MISC;
			break;
		case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_TABLE | ATT_FOREIGN_TABLE);
			/* 在执行阶段发生递归 */
			/* 除了保存递归标志之外，不需要命令特定的准备 */
			if (fc_recurse)
				fc_cmd->subtype = AT_ValidateConstraintRecurse;
			fc_pass = AT_PASS_MISC;
			break;
		case AT_ReplicaIdentity:	/* REPLICA IDENTITY ... */
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_TABLE | ATT_MATVIEW);
			fc_pass = AT_PASS_MISC;
			/* 此命令永远不会递归 */
			/* 无需特定命令的准备 */
			break;
		case AT_EnableTrig:		/* ENABLE TRIGGER variants */
		case AT_EnableAlwaysTrig:
		case AT_EnableReplicaTrig:
		case AT_EnableTrigAll:
		case AT_EnableTrigUser:
		case AT_DisableTrig:	/* 禁用触发器 variants */
		case AT_DisableTrigAll:
		case AT_DisableTrigUser:
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_TABLE | ATT_FOREIGN_TABLE);
			/* 为阶段 2 设置递归；无需其他准备 */
			if (fc_recurse)
				fc_cmd->recurse = true;
			fc_pass = AT_PASS_MISC;
			break;
		case AT_EnableRule:		/* 启用/禁用规则 variants */
		case AT_EnableAlwaysRule:
		case AT_EnableReplicaRule:
		case AT_DisableRule:
		case AT_AddOf:			/* OF */
		case AT_DropOf:			/* NOT OF */
		case AT_EnableRowSecurity:
		case AT_DisableRowSecurity:
		case AT_ForceRowSecurity:
		case AT_NoForceRowSecurity:
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_TABLE);
			/* 这些命令不会递归 */
			/* 无需特定命令的准备 */
			fc_pass = AT_PASS_MISC;
			break;
		case AT_GenericOptions:
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_FOREIGN_TABLE);
			/* 无需特定命令的准备 */
			fc_pass = AT_PASS_MISC;
			break;
		case AT_AttachPartition:
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_TABLE | ATT_PARTITIONED_INDEX);
			/* 无需特定命令的准备 */
			fc_pass = AT_PASS_MISC;
			break;
		case AT_DetachPartition:
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_TABLE);
			/* 无需特定命令的准备 */
			fc_pass = AT_PASS_MISC;
			break;
		case AT_DetachPartitionFinalize:
			fc_ATSimplePermissions(fc_cmd->subtype, fc_rel, ATT_TABLE);
			/* 无需特定命令的准备 */
			fc_pass = AT_PASS_MISC;
			break;
		default:				/* 哦，糟糕 */
			elog(ERROR, "unrecognized alter table type: %d",
				 (int) fc_cmd->subtype);
			fc_pass = AT_PASS_UNSET;	/* 保持编译器安静 */
			break;
	}
	Assert(fc_pass > AT_PASS_UNSET);

	/* 将子命令添加到阶段 2 的适当列表中 */
	fc_tab->subcmds[fc_pass] = lappend(fc_tab->subcmds[fc_pass], fc_cmd);
}

/*
 * ATRewriteCatalogs
 *
 * ALTER TABLE 阶段 2 操作的交通警察。子命令以“安全”的执行顺序调度
 * （旨在避免不必要的冲突）。
 */
static void fc_ATRewriteCatalogs(List **fc_wqueue, LOCKMODE fc_lockmode,
				  AlterTableUtilityContext *fc_context)
{
	int			fc_pass;
	ListCell   *fc_ltab;

	/*
	 * 我们“并行”处理所有表，一次一个遍。这样做是必要的，因为我们可能
	 * 需要将工作从一个表传播到另一个表（具体来说，外键的主键的 ALTER TYPE
	 * 必须将外键约束的重新添加调度到其他表中）。然而，工作只能传播到后续
	 * 传递中。
	 */
	for (fc_pass = 0; fc_pass < AT_NUM_PASSES; fc_pass++)
	{
		/* 遍历每个需要处理的表 */
		foreach(fc_ltab, *fc_wqueue)
		{
			AlteredTableInfo *fc_tab = (AlteredTableInfo *) lfirst(fc_ltab);
			List	   *fc_subcmds = fc_tab->subcmds[fc_pass];
			ListCell   *fc_lcmd;

			if (fc_subcmds == NIL)
				continue;

			/*
			 * 打开关系并将其存储在 tab 中。这允许子程序在必要时关闭和重新打开。
			 * 阶段 1 已获得适当的锁，无需再次获取。
			 */
			fc_tab->rel = relation_open(fc_tab->relid, NoLock);

			foreach(fc_lcmd, fc_subcmds)
				fc_ATExecCmd(fc_wqueue, fc_tab,
						  lfirst_node(AlterTableCmd, fc_lcmd),
						  fc_lockmode, fc_pass, fc_context);

			/*
			 * 在 ALTER TYPE 传递后，进行清理工作（因为如果一个表的多个列被更改，
			 * 应该仅进行一次此操作，所以此操作未在 ATExecAlterColumnType 中完成）。
			 */
			if (fc_pass == AT_PASS_ALTER_TYPE)
				fc_ATPostAlterTypeCleanup(fc_wqueue, fc_tab, fc_lockmode);

			if (fc_tab->rel)
			{
				relation_close(fc_tab->rel, NoLock);
				fc_tab->rel = NULL;
			}
		}
	}

	/* 检查是否必须添加 toast 表。 */
	foreach(fc_ltab, *fc_wqueue)
	{
		AlteredTableInfo *fc_tab = (AlteredTableInfo *) lfirst(fc_ltab);

		/*
		 * 如果该表是 ATTACH PARTITION 命令的源表，我们没有修改任何
		 * 会改变其 toast 要求的内容，因此无需检查。
		 */
		if (((fc_tab->relkind == RELKIND_RELATION ||
			  fc_tab->relkind == RELKIND_PARTITIONED_TABLE) &&
			 fc_tab->partition_constraint == NULL) ||
			fc_tab->relkind == RELKIND_MATVIEW)
			AlterTableCreateToastTable(fc_tab->relid, (Datum) 0, fc_lockmode);
	}
}

/*
 * ATExecCmd：将子命令调度到适当的执行例程
 */
static void fc_ATExecCmd(List **fc_wqueue, AlteredTableInfo *fc_tab,
		  AlterTableCmd *fc_cmd, LOCKMODE fc_lockmode, int fc_cur_pass,
		  AlterTableUtilityContext *fc_context)
{
	ObjectAddress fc_address = InvalidObjectAddress;
	Relation	fc_rel = fc_tab->rel;

	switch (fc_cmd->subtype)
	{
		case AT_AddColumn:		/* ADD COLUMN */
		case AT_AddColumnToView:	/* 通过 CREATE OR REPLACE VIEW 添加列 */
			fc_address = fc_ATExecAddColumn(fc_wqueue, fc_tab, fc_rel, &fc_cmd,
									  false, false,
									  fc_lockmode, fc_cur_pass, fc_context);
			break;
		case AT_AddColumnRecurse:
			fc_address = fc_ATExecAddColumn(fc_wqueue, fc_tab, fc_rel, &fc_cmd,
									  true, false,
									  fc_lockmode, fc_cur_pass, fc_context);
			break;
		case AT_ColumnDefault:	/* 修改列默认值 */
			fc_address = fc_ATExecColumnDefault(fc_rel, fc_cmd->name, fc_cmd->def, fc_lockmode);
			break;
		case AT_CookedColumnDefault:	/* 添加一个预处理的默认值 */
			fc_address = fc_ATExecCookedColumnDefault(fc_rel, fc_cmd->num, fc_cmd->def);
			break;
		case AT_AddIdentity:
			fc_cmd = fc_ATParseTransformCmd(fc_wqueue, fc_tab, fc_rel, fc_cmd, false, fc_lockmode,
									  fc_cur_pass, fc_context);
			Assert(fc_cmd != NULL);
			fc_address = fc_ATExecAddIdentity(fc_rel, fc_cmd->name, fc_cmd->def, fc_lockmode);
			break;
		case AT_SetIdentity:
			fc_cmd = fc_ATParseTransformCmd(fc_wqueue, fc_tab, fc_rel, fc_cmd, false, fc_lockmode,
									  fc_cur_pass, fc_context);
			Assert(fc_cmd != NULL);
			fc_address = fc_ATExecSetIdentity(fc_rel, fc_cmd->name, fc_cmd->def, fc_lockmode);
			break;
		case AT_DropIdentity:
			fc_address = fc_ATExecDropIdentity(fc_rel, fc_cmd->name, fc_cmd->missing_ok, fc_lockmode);
			break;
		case AT_DropNotNull:	/* ALTER COLUMN DROP NOT NULL */
			fc_address = fc_ATExecDropNotNull(fc_rel, fc_cmd->name, fc_lockmode);
			break;
		case AT_SetNotNull:		/* ALTER COLUMN SET NOT NULL */
			fc_address = fc_ATExecSetNotNull(fc_tab, fc_rel, fc_cmd->name, fc_lockmode);
			break;
		case AT_CheckNotNull:	/* 检查列是否已标记为 NOT NULL */
			fc_ATExecCheckNotNull(fc_tab, fc_rel, fc_cmd->name, fc_lockmode);
			break;
		case AT_DropExpression:
			fc_address = fc_ATExecDropExpression(fc_rel, fc_cmd->name, fc_cmd->missing_ok, fc_lockmode);
			break;
		case AT_SetStatistics:	/* ALTER COLUMN SET STATISTICS */
			fc_address = fc_ATExecSetStatistics(fc_rel, fc_cmd->name, fc_cmd->num, fc_cmd->def, fc_lockmode);
			break;
		case AT_SetOptions:		/* ALTER COLUMN SET ( options ) */
			fc_address = fc_ATExecSetOptions(fc_rel, fc_cmd->name, fc_cmd->def, false, fc_lockmode);
			break;
		case AT_ResetOptions:	/* ALTER COLUMN RESET ( options ) */
			fc_address = fc_ATExecSetOptions(fc_rel, fc_cmd->name, fc_cmd->def, true, fc_lockmode);
			break;
		case AT_SetStorage:		/* ALTER COLUMN SET STORAGE */
			fc_address = fc_ATExecSetStorage(fc_rel, fc_cmd->name, fc_cmd->def, fc_lockmode);
			break;
		case AT_SetCompression:
			fc_address = fc_ATExecSetCompression(fc_tab, fc_rel, fc_cmd->name, fc_cmd->def,
										   fc_lockmode);
			break;
		case AT_DropColumn:		/* DROP COLUMN */
			fc_address = fc_ATExecDropColumn(fc_wqueue, fc_rel, fc_cmd->name,
									   fc_cmd->behavior, false, false,
									   fc_cmd->missing_ok, fc_lockmode,
									   NULL);
			break;
		case AT_DropColumnRecurse:	/* 递归删除列 */
			fc_address = fc_ATExecDropColumn(fc_wqueue, fc_rel, fc_cmd->name,
									   fc_cmd->behavior, true, false,
									   fc_cmd->missing_ok, fc_lockmode,
									   NULL);
			break;
		case AT_AddIndex:		/* ADD INDEX */
			fc_address = fc_ATExecAddIndex(fc_tab, fc_rel, (IndexStmt *) fc_cmd->def, false,
									 fc_lockmode);
			break;
		case AT_ReAddIndex:		/* ADD INDEX */
			fc_address = fc_ATExecAddIndex(fc_tab, fc_rel, (IndexStmt *) fc_cmd->def, true,
									 fc_lockmode);
			break;
		case AT_ReAddStatistics:	/* 添加统计信息 */
			fc_address = fc_ATExecAddStatistics(fc_tab, fc_rel, (CreateStatsStmt *) fc_cmd->def,
										  true, fc_lockmode);
			break;
		case AT_AddConstraint:	/* ADD CONSTRAINT */
			/* 仅在初步检查期间变换命令 */
			if (fc_cur_pass == AT_PASS_ADD_CONSTR)
				fc_cmd = fc_ATParseTransformCmd(fc_wqueue, fc_tab, fc_rel, fc_cmd,
										  false, fc_lockmode,
										  fc_cur_pass, fc_context);
			/* 根据约束类型，可能现在没有更多的工作要做 */
			if (fc_cmd != NULL)
				fc_address =
					fc_ATExecAddConstraint(fc_wqueue, fc_tab, fc_rel,
										(Constraint *) fc_cmd->def,
										false, false, fc_lockmode);
			break;
		case AT_AddConstraintRecurse:	/* 递归添加约束 */
			/* 仅在初步检查期间变换命令 */
			if (fc_cur_pass == AT_PASS_ADD_CONSTR)
				fc_cmd = fc_ATParseTransformCmd(fc_wqueue, fc_tab, fc_rel, fc_cmd,
										  true, fc_lockmode,
										  fc_cur_pass, fc_context);
			/* 根据约束类型，可能现在没有更多的工作要做 */
			if (fc_cmd != NULL)
				fc_address =
					fc_ATExecAddConstraint(fc_wqueue, fc_tab, fc_rel,
										(Constraint *) fc_cmd->def,
										true, false, fc_lockmode);
			break;
		case AT_ReAddConstraint:	/* 重新添加现有检查约束 */
			fc_address =
				fc_ATExecAddConstraint(fc_wqueue, fc_tab, fc_rel, (Constraint *) fc_cmd->def,
									true, true, fc_lockmode);
			break;
		case AT_ReAddDomainConstraint:	/* 重新添加现有域检查约束 */
			fc_address =
				AlterDomainAddConstraint(((AlterDomainStmt *) fc_cmd->def)->typeName,
										 ((AlterDomainStmt *) fc_cmd->def)->def,
										 NULL);
			break;
		case AT_ReAddComment:	/* 重新添加现有注释 */
			fc_address = CommentObject((CommentStmt *) fc_cmd->def);
			break;
		case AT_AddIndexConstraint: /* ADD CONSTRAINT USING INDEX */
			fc_address = fc_ATExecAddIndexConstraint(fc_tab, fc_rel, (IndexStmt *) fc_cmd->def,
											   fc_lockmode);
			break;
		case AT_AlterConstraint:	/* ALTER CONSTRAINT */
			fc_address = fc_ATExecAlterConstraint(fc_rel, fc_cmd, false, false, fc_lockmode);
			break;
		case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */
			fc_address = fc_ATExecValidateConstraint(fc_wqueue, fc_rel, fc_cmd->name, false,
											   false, fc_lockmode);
			break;
		case AT_ValidateConstraintRecurse:	/* 具有递归的验证约束 */
			fc_address = fc_ATExecValidateConstraint(fc_wqueue, fc_rel, fc_cmd->name, true,
											   false, fc_lockmode);
			break;
		case AT_DropConstraint: /* DROP CONSTRAINT */
			fc_ATExecDropConstraint(fc_rel, fc_cmd->name, fc_cmd->behavior,
								 false, false,
								 fc_cmd->missing_ok, fc_lockmode);
			break;
		case AT_DropConstraintRecurse:	/* 具有递归的删除约束 */
			fc_ATExecDropConstraint(fc_rel, fc_cmd->name, fc_cmd->behavior,
								 true, false,
								 fc_cmd->missing_ok, fc_lockmode);
			break;
		case AT_AlterColumnType:	/* ALTER COLUMN TYPE */
			/* 解析转换在之前已完成 */
			fc_address = fc_ATExecAlterColumnType(fc_tab, fc_rel, fc_cmd, fc_lockmode);
			break;
		case AT_AlterColumnGenericOptions:	/* 修改列选项 */
			fc_address =
				fc_ATExecAlterColumnGenericOptions(fc_rel, fc_cmd->name,
												(List *) fc_cmd->def, fc_lockmode);
			break;
		case AT_ChangeOwner:	/* ALTER OWNER */
			ATExecChangeOwner(RelationGetRelid(fc_rel),
							  get_rolespec_oid(fc_cmd->newowner, false),
							  false, fc_lockmode);
			break;
		case AT_ClusterOn:		/* CLUSTER ON */
			fc_address = fc_ATExecClusterOn(fc_rel, fc_cmd->name, fc_lockmode);
			break;
		case AT_DropCluster:	/* SET WITHOUT CLUSTER */
			fc_ATExecDropCluster(fc_rel, fc_lockmode);
			break;
		case AT_SetLogged:		/* SET LOGGED */
		case AT_SetUnLogged:	/* SET UNLOGGED */
			break;
		case AT_DropOids:		/* SET WITHOUT OIDS */
			/* 这里没有任何操作，OID列不再存在 */
			break;
		case AT_SetAccessMethod:	/* SET ACCESS METHOD */
			/* 在第3阶段特别处理 */
			break;
		case AT_SetTableSpace:	/* SET TABLESPACE */

			/*
			 * 仅对分区表和索引执行此操作，因为这只是目录更改。
			 * 其他具有存储的关系类型由第3阶段处理。
			 */
			if (fc_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
				fc_rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
				fc_ATExecSetTableSpaceNoStorage(fc_rel, fc_tab->newTableSpace);

			break;
		case AT_SetRelOptions:	/* SET (...) */
		case AT_ResetRelOptions:	/* RESET (...) */
		case AT_ReplaceRelOptions:	/* 替换整个选项列表 */
			fc_ATExecSetRelOptions(fc_rel, (List *) fc_cmd->def, fc_cmd->subtype, fc_lockmode);
			break;
		case AT_EnableTrig:		/* 启用触发器名称 */
			fc_ATExecEnableDisableTrigger(fc_rel, fc_cmd->name,
									   TRIGGER_FIRES_ON_ORIGIN, false,
									   fc_cmd->recurse,
									   fc_lockmode);
			break;
		case AT_EnableAlwaysTrig:	/* 始终启用触发器名称 */
			fc_ATExecEnableDisableTrigger(fc_rel, fc_cmd->name,
									   TRIGGER_FIRES_ALWAYS, false,
									   fc_cmd->recurse,
									   fc_lockmode);
			break;
		case AT_EnableReplicaTrig:	/* 启用副本触发器名称 */
			fc_ATExecEnableDisableTrigger(fc_rel, fc_cmd->name,
									   TRIGGER_FIRES_ON_REPLICA, false,
									   fc_cmd->recurse,
									   fc_lockmode);
			break;
		case AT_DisableTrig:	/* 禁用触发器名称 */
			fc_ATExecEnableDisableTrigger(fc_rel, fc_cmd->name,
									   TRIGGER_DISABLED, false,
									   fc_cmd->recurse,
									   fc_lockmode);
			break;
		case AT_EnableTrigAll:	/* 启用所有触发器 */
			fc_ATExecEnableDisableTrigger(fc_rel, NULL,
									   TRIGGER_FIRES_ON_ORIGIN, false,
									   fc_cmd->recurse,
									   fc_lockmode);
			break;
		case AT_DisableTrigAll: /* 禁用所有触发器 */
			fc_ATExecEnableDisableTrigger(fc_rel, NULL,
									   TRIGGER_DISABLED, false,
									   fc_cmd->recurse,
									   fc_lockmode);
			break;
		case AT_EnableTrigUser: /* 启用用户触发器 */
			fc_ATExecEnableDisableTrigger(fc_rel, NULL,
									   TRIGGER_FIRES_ON_ORIGIN, true,
									   fc_cmd->recurse,
									   fc_lockmode);
			break;
		case AT_DisableTrigUser:	/* 禁用用户触发器 */
			fc_ATExecEnableDisableTrigger(fc_rel, NULL,
									   TRIGGER_DISABLED, true,
									   fc_cmd->recurse,
									   fc_lockmode);
			break;

		case AT_EnableRule:		/* 启用规则名称 */
			fc_ATExecEnableDisableRule(fc_rel, fc_cmd->name,
									RULE_FIRES_ON_ORIGIN, fc_lockmode);
			break;
		case AT_EnableAlwaysRule:	/* 始终启用规则名称 */
			fc_ATExecEnableDisableRule(fc_rel, fc_cmd->name,
									RULE_FIRES_ALWAYS, fc_lockmode);
			break;
		case AT_EnableReplicaRule:	/* 启用副本规则名称 */
			fc_ATExecEnableDisableRule(fc_rel, fc_cmd->name,
									RULE_FIRES_ON_REPLICA, fc_lockmode);
			break;
		case AT_DisableRule:	/* 禁用规则名称 */
			fc_ATExecEnableDisableRule(fc_rel, fc_cmd->name,
									RULE_DISABLED, fc_lockmode);
			break;

		case AT_AddInherit:
			fc_address = fc_ATExecAddInherit(fc_rel, (RangeVar *) fc_cmd->def, fc_lockmode);
			break;
		case AT_DropInherit:
			fc_address = fc_ATExecDropInherit(fc_rel, (RangeVar *) fc_cmd->def, fc_lockmode);
			break;
		case AT_AddOf:
			fc_address = fc_ATExecAddOf(fc_rel, (TypeName *) fc_cmd->def, fc_lockmode);
			break;
		case AT_DropOf:
			fc_ATExecDropOf(fc_rel, fc_lockmode);
			break;
		case AT_ReplicaIdentity:
			fc_ATExecReplicaIdentity(fc_rel, (ReplicaIdentityStmt *) fc_cmd->def, fc_lockmode);
			break;
		case AT_EnableRowSecurity:
			fc_ATExecSetRowSecurity(fc_rel, true);
			break;
		case AT_DisableRowSecurity:
			fc_ATExecSetRowSecurity(fc_rel, false);
			break;
		case AT_ForceRowSecurity:
			fc_ATExecForceNoForceRowSecurity(fc_rel, true);
			break;
		case AT_NoForceRowSecurity:
			fc_ATExecForceNoForceRowSecurity(fc_rel, false);
			break;
		case AT_GenericOptions:
			fc_ATExecGenericOptions(fc_rel, (List *) fc_cmd->def);
			break;
		case AT_AttachPartition:
			fc_cmd = fc_ATParseTransformCmd(fc_wqueue, fc_tab, fc_rel, fc_cmd, false, fc_lockmode,
									  fc_cur_pass, fc_context);
			Assert(fc_cmd != NULL);
			if (fc_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
				fc_ATExecAttachPartition(fc_wqueue, fc_rel, (PartitionCmd *) fc_cmd->def,
									  fc_context);
			else
				fc_ATExecAttachPartitionIdx(fc_wqueue, fc_rel,
										 ((PartitionCmd *) fc_cmd->def)->name);
			break;
		case AT_DetachPartition:
			fc_cmd = fc_ATParseTransformCmd(fc_wqueue, fc_tab, fc_rel, fc_cmd, false, fc_lockmode,
									  fc_cur_pass, fc_context);
			Assert(fc_cmd != NULL);
			/* ATPrepCmd 确保必须是一个表 */
			Assert(fc_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
			fc_ATExecDetachPartition(fc_wqueue, fc_tab, fc_rel,
								  ((PartitionCmd *) fc_cmd->def)->name,
								  ((PartitionCmd *) fc_cmd->def)->concurrent);
			break;
		case AT_DetachPartitionFinalize:
			fc_address = fc_ATExecDetachPartitionFinalize(fc_rel, ((PartitionCmd *) fc_cmd->def)->name);
			break;
		default:				/* 哦，糟糕 */
			elog(ERROR, "unrecognized alter table type: %d",
				 (int) fc_cmd->subtype);
			break;
	}

	/*
	 * 将子命令报告给相关的事件触发器。
	 */
	if (fc_cmd)
		EventTriggerCollectAlterTableSubcmd((Node *) fc_cmd, fc_address);

	/*
	 * 增加命令计数器，以确保序列中的下一个子命令
	 * 可以看到到目前为止的更改
	 */
	CommandCounterIncrement();
}

/*
 * ATParseTransformCmd：对一个子命令执行解析转换
 *
 * 如果有，则返回转换后的子命令树，否则返回 NULL。
 *
 * 解析器可能会在原始子命令之前或之后返回额外的 AlterTableCmd(s) 和/或其他
 * 实用语句。其他 AlterTableCmd 被调度到 AlteredTableInfo 的相应槽中（最好比当前的
 * 还要晚的阶段）。预计在 AlterTableCmd 之前发生的实用语句立即执行。
 * 预计在之后发生的则被添加到 tab->afterStmts 列表中，以便在最后执行。
 */
static AlterTableCmd *
fc_ATParseTransformCmd(List **fc_wqueue, AlteredTableInfo *fc_tab, Relation fc_rel,
					AlterTableCmd *fc_cmd, bool fc_recurse, LOCKMODE fc_lockmode,
					int fc_cur_pass, AlterTableUtilityContext *fc_context)
{
	AlterTableCmd *fc_newcmd = NULL;
	AlterTableStmt *fc_atstmt = makeNode(AlterTableStmt);
	List	   *fc_beforeStmts;
	List	   *fc_afterStmts;
	ListCell   *fc_lc;

	/* 创建一个仅包含此子命令和此表的 AlterTableStmt */
	fc_atstmt->relation =
		makeRangeVar(get_namespace_name(RelationGetNamespace(fc_rel)),
					 pstrdup(RelationGetRelationName(fc_rel)),
					 -1);
	fc_atstmt->relation->inh = fc_recurse;
	fc_atstmt->cmds = list_make1(fc_cmd);
	fc_atstmt->objtype = OBJECT_TABLE; /* 这里不必挑剔 */
	fc_atstmt->missing_ok = false;

	/* 转换 AlterTableStmt */
	fc_atstmt = transformAlterTableStmt(RelationGetRelid(fc_rel),
									 fc_atstmt,
									 fc_context->queryString,
									 &fc_beforeStmts,
									 &fc_afterStmts);

	/* 执行在这些子命令之前应该发生的任何语句 */
	foreach(fc_lc, fc_beforeStmts)
	{
		Node	   *fc_stmt = (Node *) lfirst(fc_lc);

		ProcessUtilityForAlterTable(fc_stmt, fc_context);
		CommandCounterIncrement();
	}

	/* 检查转换后的子命令并适当地调度它们 */
	foreach(fc_lc, fc_atstmt->cmds)
	{
		AlterTableCmd *fc_cmd2 = lfirst_node(AlterTableCmd, fc_lc);
		int			fc_pass;

		/*
		 * 此开关仅需涵盖可以通过 parse_utilcmd.c 添加的子命令类型；
		 * 否则，我们将使用默认策略立即执行子命令，作为原始子命令的替代。
		 * （不过，请注意，这确实导致 AT_AddConstraint 子命令被重新调度到后续阶段，
		 * 这对于索引和外键约束非常重要。）
		 *
		 * 我们假设对于添加的子命令无需进行任何阶段1检查。
		 */
		switch (fc_cmd2->subtype)
		{
			case AT_SetNotNull:
				/* 需要命令特定的递归决策 */
				fc_ATPrepSetNotNull(fc_wqueue, fc_rel, fc_cmd2,
								 fc_recurse, false,
								 fc_lockmode, fc_context);
				fc_pass = AT_PASS_COL_ATTRS;
				break;
			case AT_AddIndex:
				/* 此命令永远不会递归 */
				/* 无需特定命令的准备 */
				fc_pass = AT_PASS_ADD_INDEX;
				break;
			case AT_AddIndexConstraint:
				/* 此命令永远不会递归 */
				/* 无需特定命令的准备 */
				fc_pass = AT_PASS_ADD_INDEXCONSTR;
				break;
			case AT_AddConstraint:
				/* 在执行阶段发生递归 */
				if (fc_recurse)
					fc_cmd2->subtype = AT_AddConstraintRecurse;
				switch (castNode(Constraint, fc_cmd2->def)->contype)
				{
					case CONSTR_PRIMARY:
					case CONSTR_UNIQUE:
					case CONSTR_EXCLUSION:
						fc_pass = AT_PASS_ADD_INDEXCONSTR;
						break;
					default:
						fc_pass = AT_PASS_ADD_OTHERCONSTR;
						break;
				}
				break;
			case AT_AlterColumnGenericOptions:
				/* 此命令永远不会递归 */
				/* 无需特定命令的准备 */
				fc_pass = AT_PASS_MISC;
				break;
			default:
				fc_pass = fc_cur_pass;
				break;
		}

		if (fc_pass < fc_cur_pass)
		{
			/* 不能调度到我们已经完成的阶段 */
			elog(ERROR, "ALTER TABLE scheduling failure: too late for pass %d",
				 fc_pass);
		}
		else if (fc_pass > fc_cur_pass)
		{
			/* 好的，将其排队以备后用 */
			fc_tab->subcmds[fc_pass] = lappend(fc_tab->subcmds[fc_pass], fc_cmd2);
		}
		else
		{
			/*
			 * 我们应该最多看到一个当前阶段的子命令，
			 * 这是原始子命令的转换版本。
			 */
			if (fc_newcmd == NULL && fc_cmd->subtype == fc_cmd2->subtype)
			{
				/* 找到我们子命令的转换版本 */
				fc_newcmd = fc_cmd2;
			}
			else
				elog(ERROR, "ALTER TABLE scheduling failure: bogus item for pass %d",
					 fc_pass);
		}
	}

	/* 将任何后陈述排队以在最后发生 */
	fc_tab->afterStmts = list_concat(fc_tab->afterStmts, fc_afterStmts);

	return fc_newcmd;
}

/*
 * ATRewriteTables: ALTER TABLE 第3阶段
 */
static void fc_ATRewriteTables(AlterTableStmt *fc_parsetree, List **fc_wqueue, LOCKMODE fc_lockmode,
				AlterTableUtilityContext *fc_context)
{
	ListCell   *fc_ltab;

	/* 检查或重写每个需要处理的表 */
	foreach(fc_ltab, *fc_wqueue)
	{
		AlteredTableInfo *fc_tab = (AlteredTableInfo *) lfirst(fc_ltab);

		/* 没有存储的关系可以在这里忽略 */
		if (!RELKIND_HAS_STORAGE(fc_tab->relkind))
			continue;

		/*
		 * 如果我们更改列的数据类型，则必须将操作传播到
		 * 使用该表行类型作为列类型的表。
		 * 当我们添加一个带有默认值的列时，tab->newvals 也将是非 NULL。
		 * 我们选择禁止这种情况，
		 * 因为复合类型最终可能支持默认值。
		 *
		 * (最终我们可能需要检查复合类型
		 * 依赖关系，即使我们只是扫描表而没有重写，但目前复合类型
		 * 不强制执行任何约束，因此在 ALTER 期间强制执行它们
		 * 既不必要也不合适。)
		 */
		if (fc_tab->newvals != NIL || fc_tab->rewrite > 0)
		{
			Relation	fc_rel;

			fc_rel = table_open(fc_tab->relid, NoLock);
			find_composite_type_dependencies(fc_rel->rd_rel->reltype, fc_rel, NULL);
			table_close(fc_rel, NoLock);
		}

		/*
		 * 只有在至少一个列需要重新计算，或者我们更改其持久性或访问方法时，
		 * 才需要重写表。
		 *
		 * 更改持久性时需要重写有两个原因：一方面，我们需要确保
		 * 属于两个关系的缓冲区被正确标记为 BM_PERMANENT 或不为 BM_PERMANENT。
		 * 另一方面，由于重写会创建并分配一个新的 relfilenode，
		 * 我们会根据需要自动创建或删除关系的 init fork。
		 */
		if (fc_tab->rewrite > 0 && fc_tab->relkind != RELKIND_SEQUENCE)
		{
			/* 构建临时关系并复制数据 */
			Relation	fc_OldHeap;
			Oid			fc_OIDNewHeap;
			Oid			fc_NewAccessMethod;
			Oid			fc_NewTableSpace;
			char		fc_persistence;

			fc_OldHeap = table_open(fc_tab->relid, NoLock);

			/*
			 * 我们不支持重写系统目录；有太多的边缘案例且收益太小。
			 * 尤其是这肯定无法应用于映射目录。
			 */
			if (IsSystemRelation(fc_OldHeap))
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("cannot rewrite system relation \"%s\"",
								RelationGetRelationName(fc_OldHeap))));

			if (RelationIsUsedAsCatalogTable(fc_OldHeap))
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("cannot rewrite table \"%s\" used as a catalog table",
								RelationGetRelationName(fc_OldHeap))));

			/*
			 * 不允许对其他后端的临时表进行重写……它们的
			 * 本地缓冲区管理器将无法处理。(这与 CheckAlterTableIsSafe 中的
			 * 检查是冗余的，但为了安全，我们在这里也进行检查。)
			 */
			if (RELATION_IS_OTHER_TEMP(fc_OldHeap))
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("cannot rewrite temporary tables of other sessions")));

			/*
			 * 选择目标表空间（与原始相同，除非用户请求更改）
			 */
			if (fc_tab->newTableSpace)
				fc_NewTableSpace = fc_tab->newTableSpace;
			else
				fc_NewTableSpace = fc_OldHeap->rd_rel->reltablespace;

			/*
			 * 选择目标访问方法（与原始相同，除非用户请求更改）
			 */
			if (OidIsValid(fc_tab->newAccessMethod))
				fc_NewAccessMethod = fc_tab->newAccessMethod;
			else
				fc_NewAccessMethod = fc_OldHeap->rd_rel->relam;

			/*
			 * 选择临时表的持久性（与原始相同，除非用户请求更改）
			 */
			fc_persistence = fc_tab->chgPersistence ?
				fc_tab->newrelpersistence : fc_OldHeap->rd_rel->relpersistence;

			table_close(fc_OldHeap, NoLock);

			/*
			 * 现在触发一次事件触发器，在实际重写表之前。
			 *
			 * 我们在任何地方都不支持嵌套命令的事件触发器，包括这里，并且当来自
			 * AlterTableInternal 时，parsetree 被设置为 NULL。
			 *
			 * 并且仅触发一次。
			 */
			if (fc_parsetree)
				EventTriggerTableRewrite((Node *) fc_parsetree,
										 fc_tab->relid,
										 fc_tab->rewrite);

			/*
			 * 创建一个临时表，以接收修改后的数据。
			 *
			 * 确保它被正确标记为记录或非记录。我们必须在这里执行此操作，以便
			 * 新 relfilenode 的缓冲区具有正确的持久性设置，同时确保原始
			 * filenode 的缓冲区将在正确的设置（即原始设置）下读取。
			 * 否则，重写后的回滚可能会导致原始 filenode 的缓冲区具有错误的
			 * 持久性设置。
			 *
			 * 注意：这依赖于 swap_relation_files() 也交换持久性。对于
			 * pg_class 这将不起作用，但无论如何它不能是非记录的。
			 */
			fc_OIDNewHeap = make_new_heap(fc_tab->relid, fc_NewTableSpace, fc_NewAccessMethod,
									   fc_persistence, fc_lockmode);

			/*
			 * 将堆数据复制到新表中，进行所需的修改，并根据 ALTER TABLE
			 * 命令生成的新约束测试表中的当前数据。
			 */
			fc_ATRewriteTable(fc_tab, fc_OIDNewHeap, fc_lockmode);

			/*
			 * 交换旧堆和新堆的物理文件，然后重建索引并丢弃旧堆。我们可以
			 * 使用 RecentXmin 作为表的新 relfrozenxid，因为我们在
			 * ATRewriteTable 中重写了所有元组，因此表中没有较旧的
			 * Xid。同时，我们绝不会试图通过内容交换 toast 表，
			 * 因为我们无意让此代码在系统目录上工作。
			 */
			finish_heap_swap(fc_tab->relid, fc_OIDNewHeap,
							 false, false, true,
							 !OidIsValid(fc_tab->newTableSpace),
							 RecentXmin,
							 ReadNextMultiXactId(),
							 fc_persistence);

			InvokeObjectPostAlterHook(RelationRelationId, fc_tab->relid, 0);
		}
		else if (fc_tab->rewrite > 0 && fc_tab->relkind == RELKIND_SEQUENCE)
		{
			if (fc_tab->chgPersistence)
				SequenceChangePersistence(fc_tab->relid, fc_tab->newrelpersistence);
		}
		else
		{
			/*
			 * 如果需要，对表中的当前数据根据 ALTER TABLE 命令生成的新
			 * 约束进行测试，但不重建数据。
			 */
			if (fc_tab->constraints != NIL || fc_tab->verify_new_notnull ||
				fc_tab->partition_constraint != NULL)
				fc_ATRewriteTable(fc_tab, InvalidOid, fc_lockmode);

			/*
			 * 如果我们使用了 SET TABLESPACE 但没有理由重建元组，
			 * 只需进行逐块复制。
			 */
			if (fc_tab->newTableSpace)
				fc_ATExecSetTableSpace(fc_tab->relid, fc_tab->newTableSpace, fc_lockmode);
		}

		/*
		 * 还要更改拥有的序列的持久性，以使其与表的持久性相匹配。
		 */
		if (fc_tab->chgPersistence)
		{
			List	   *fc_seqlist = getOwnedSequences(fc_tab->relid);
			ListCell   *fc_lc;

			foreach(fc_lc, fc_seqlist)
			{
				Oid			fc_seq_relid = lfirst_oid(fc_lc);

				SequenceChangePersistence(fc_seq_relid, fc_tab->newrelpersistence);
			}
		}
	}

	/*
	 * 外键约束在最后一次检查中进行检查，因为 (a) 通常最好分别检查
	 * 每一个，以及 (b) 理论上可能我们已经修改了外键的两个关系，
	 * 我们最好在尝试读取表之前已完成两个重写。
	 */
	foreach(fc_ltab, *fc_wqueue)
	{
		AlteredTableInfo *fc_tab = (AlteredTableInfo *) lfirst(fc_ltab);
		Relation	fc_rel = NULL;
		ListCell   *fc_lcon;

		/* 没有存储的关系也可以在这里被忽略 */
		if (!RELKIND_HAS_STORAGE(fc_tab->relkind))
			continue;

		foreach(fc_lcon, fc_tab->constraints)
		{
			NewConstraint *fc_con = lfirst(fc_lcon);

			if (fc_con->contype == CONSTR_FOREIGN)
			{
				Constraint *fc_fkconstraint = (Constraint *) fc_con->qual;
				Relation	fc_refrel;

				if (fc_rel == NULL)
				{
					/* 长时间已锁定，不需要再锁定一次 */
					fc_rel = table_open(fc_tab->relid, NoLock);
				}

				fc_refrel = table_open(fc_con->refrelid, RowShareLock);

				fc_validateForeignKeyConstraint(fc_fkconstraint->conname, fc_rel, fc_refrel,
											 fc_con->refindid,
											 fc_con->conid);

				/*
				 * 无需将约束行标记为验证过，我们在之前插入行时
				 * 已经这样做了。
				 */

				table_close(fc_refrel, NoLock);
			}
		}

		if (fc_rel)
			table_close(fc_rel, NoLock);
	}

	/* 最后，运行任何排队的 afterStmts */
	foreach(fc_ltab, *fc_wqueue)
	{
		AlteredTableInfo *fc_tab = (AlteredTableInfo *) lfirst(fc_ltab);
		ListCell   *fc_lc;

		foreach(fc_lc, fc_tab->afterStmts)
		{
			Node	   *fc_stmt = (Node *) lfirst(fc_lc);

			ProcessUtilityForAlterTable(fc_stmt, fc_context);
			CommandCounterIncrement();
		}
	}
}

/*
 * ATRewriteTable：扫描或重写一个表
 *
 * OIDNewHeap 如果我们不需要重写，则为 InvalidOid
 */
static void fc_ATRewriteTable(AlteredTableInfo *fc_tab, Oid fc_OIDNewHeap, LOCKMODE fc_lockmode)
{
	Relation	fc_oldrel;
	Relation	fc_newrel;
	TupleDesc	fc_oldTupDesc;
	TupleDesc	fc_newTupDesc;
	bool		fc_needscan = false;
	List	   *fc_notnull_attrs;
	int			fc_i;
	ListCell   *fc_l;
	EState	   *fc_estate;
	CommandId	fc_mycid;
	BulkInsertState fc_bistate;
	int			fc_ti_options;
	ExprState  *fc_partqualstate = NULL;

	/*
	 * 打开关系。我们肯定已经锁定了现有表。
	 */
	fc_oldrel = table_open(fc_tab->relid, NoLock);
	fc_oldTupDesc = fc_tab->oldDesc;
	fc_newTupDesc = RelationGetDescr(fc_oldrel);	/* 包括所有修改 */

	if (OidIsValid(fc_OIDNewHeap))
		fc_newrel = table_open(fc_OIDNewHeap, fc_lockmode);
	else
		fc_newrel = NULL;

	/*
	 * 准备一个 BulkInsertState 及选项以供 table_tuple_insert 使用。FSM
	 * 是空的，所以不用考虑它。
	 */
	if (fc_newrel)
	{
		fc_mycid = GetCurrentCommandId(true);
		fc_bistate = GetBulkInsertState();
		fc_ti_options = TABLE_INSERT_SKIP_FSM;
	}
	else
	{
		/* 保持编译器对使用这些未初始化的变量保持安静 */
		fc_mycid = 0;
		fc_bistate = NULL;
		fc_ti_options = 0;
	}

	/*
	 * 生成约束和默认执行状态
	 */

	fc_estate = CreateExecutorState();

	/* 构建所需的表达式执行状态 */
	foreach(fc_l, fc_tab->constraints)
	{
		NewConstraint *fc_con = lfirst(fc_l);

		switch (fc_con->contype)
		{
			case CONSTR_CHECK:
				fc_needscan = true;
				fc_con->qualstate = ExecPrepareExpr((Expr *) fc_con->qual, fc_estate);
				break;
			case CONSTR_FOREIGN:
				/* 这里没有事情要做 */
				break;
			default:
				elog(ERROR, "unrecognized constraint type: %d",
					 (int) fc_con->contype);
		}
	}

	/* 为分区检查资格构建表达式执行状态 */
	if (fc_tab->partition_constraint)
	{
		fc_needscan = true;
		fc_partqualstate = ExecPrepareExpr(fc_tab->partition_constraint, fc_estate);
	}

	foreach(fc_l, fc_tab->newvals)
	{
		NewColumnValue *fc_ex = lfirst(fc_l);

		/* 表达式已计划 */
		fc_ex->exprstate = ExecInitExpr((Expr *) fc_ex->expr, NULL);
	}

	fc_notnull_attrs = NIL;
	if (fc_newrel || fc_tab->verify_new_notnull)
	{
		/*
		 * 如果我们正在重建元组或者如果我们添加了任何新的但未
		 * 验证的 NOT NULL 约束，检查所有非空约束。这
		 * 是有点多余，但它最小化了bug的风险，并且
		 * heap_attisnull 无论如何是一个相当便宜的测试。
		 */
		for (fc_i = 0; fc_i < fc_newTupDesc->natts; fc_i++)
		{
			Form_pg_attribute fc_attr = TupleDescAttr(fc_newTupDesc, fc_i);

			if (fc_attr->attnotnull && !fc_attr->attisdropped)
				fc_notnull_attrs = lappend_int(fc_notnull_attrs, fc_i);
		}
		if (fc_notnull_attrs)
			fc_needscan = true;
	}

	if (fc_newrel || fc_needscan)
	{
		ExprContext *fc_econtext;
		TupleTableSlot *fc_oldslot;
		TupleTableSlot *fc_newslot;
		TableScanDesc fc_scan;
		MemoryContext fc_oldCxt;
		List	   *fc_dropped_attrs = NIL;
		ListCell   *fc_lc;
		Snapshot	fc_snapshot;

		if (fc_newrel)
			ereport(DEBUG1,
					(errmsg_internal("rewriting table \"%s\"",
									 RelationGetRelationName(fc_oldrel))));
		else
			ereport(DEBUG1,
					(errmsg_internal("verifying table \"%s\"",
									 RelationGetRelationName(fc_oldrel))));

		if (fc_newrel)
		{
			/*
			 * 所有关于元组或页面的谓词锁即将被使
			 * 无效，因为我们移动元组。提升它们到
			 * 关系锁。
			 */
			TransferPredicateLocksToHeapRelation(fc_oldrel);
		}

		fc_econtext = GetPerTupleExprContext(fc_estate);

		/*
		 * 创建必要的元组槽。在重写时，需要两个槽，
		 * 否则一个就足够。在一个槽足够的情况下，我们
		 * 需要使用新元组描述符，否则一些约束
		 * 可能无法评估。请注意，即使元组布局是相同的，
		 * 而且不需要重写，tupDescs 也可能不相同
		 * （考虑没有默认值的 ADD COLUMN）。
		 */
		if (fc_tab->rewrite)
		{
			Assert(fc_newrel != NULL);
			fc_oldslot = MakeSingleTupleTableSlot(fc_oldTupDesc,
											   table_slot_callbacks(fc_oldrel));
			fc_newslot = MakeSingleTupleTableSlot(fc_newTupDesc,
											   table_slot_callbacks(fc_newrel));

			/*
			 * 最初将新槽中的所有列设置为 NULL，以确保
			 * 作为重写一部分添加的列初始化为 NULL。
			 * 这是必要的，因为 tab->newvals 不会包含一个
			 * 对于具有 NULL 默认值的列的表达式，例如，当添加一个
			 * 没有默认值的列时，必须与一个具有默认值
			 * 的列一起进行实际重写。
			 */
			ExecStoreAllNullTuple(fc_newslot);
		}
		else
		{
			fc_oldslot = MakeSingleTupleTableSlot(fc_newTupDesc,
											   table_slot_callbacks(fc_oldrel));
			fc_newslot = NULL;
		}

		/*
		 * 根据新的元组
		 * 描述符丢弃的任何属性可以设置为 NULL。我们预计算
		 * 丢弃属性的列表，以避免在每个元组的循环中进行此操作。
		 */
		for (fc_i = 0; fc_i < fc_newTupDesc->natts; fc_i++)
		{
			if (TupleDescAttr(fc_newTupDesc, fc_i)->attisdropped)
				fc_dropped_attrs = lappend_int(fc_dropped_attrs, fc_i);
		}

		/*
		 * 逐行扫描，必要时生成新行，然后
		 * 检查所有约束。
		 */
		fc_snapshot = RegisterSnapshot(GetLatestSnapshot());
		fc_scan = table_beginscan(fc_oldrel, fc_snapshot, 0, NULL);

		/*
		 * 切换到每个元组的内存上下文，并在生成的每个元组
		 * 上重置它，以便我们不会泄漏内存。
		 */
		fc_oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(fc_estate));

		while (table_scan_getnextslot(fc_scan, ForwardScanDirection, fc_oldslot))
		{
			TupleTableSlot *fc_insertslot;

			if (fc_tab->rewrite > 0)
			{
				/* 从旧元组中提取数据 */
				slot_getallattrs(fc_oldslot);
				ExecClearTuple(fc_newslot);

				/* 复制属性 */
				memcpy(fc_newslot->tts_values, fc_oldslot->tts_values,
					   sizeof(Datum) * fc_oldslot->tts_nvalid);
				memcpy(fc_newslot->tts_isnull, fc_oldslot->tts_isnull,
					   sizeof(bool) * fc_oldslot->tts_nvalid);

				/* 在新元组中将丢弃的属性设置为 null */
				foreach(fc_lc, fc_dropped_attrs)
					fc_newslot->tts_isnull[lfirst_int(fc_lc)] = true;

				/*
				 * 约束和 GENERATED 表达式可能引用
				 * tableoid 列，因此用所需的填充 tts_tableOid
				 * 值。（每次我们必须这样做，因为它在存储期间
				 * 被 newrel 的 OID 覆盖。）
				 */
				fc_newslot->tts_tableOid = RelationGetRelid(fc_oldrel);

				/*
				 * 处理提供的表达式以替换选定的列。
				 *
				 * 首先，评估输入来自旧
				 * 元组的表达式。
				 */
				fc_econtext->ecxt_scantuple = fc_oldslot;

				foreach(fc_l, fc_tab->newvals)
				{
					NewColumnValue *fc_ex = lfirst(fc_l);

					if (fc_ex->is_generated)
						continue;

					fc_newslot->tts_values[fc_ex->attnum - 1]
						= ExecEvalExpr(fc_ex->exprstate,
									   fc_econtext,
									   &fc_newslot->tts_isnull[fc_ex->attnum - 1]);
				}

				ExecStoreVirtualTuple(fc_newslot);

				/*
				 * 现在，评估输入来自
				 * 新元组的任何表达式。我们假设这些列不会相互引用，
				 * 因此没有顺序依赖关系。
				 */
				fc_econtext->ecxt_scantuple = fc_newslot;

				foreach(fc_l, fc_tab->newvals)
				{
					NewColumnValue *fc_ex = lfirst(fc_l);

					if (!fc_ex->is_generated)
						continue;

					fc_newslot->tts_values[fc_ex->attnum - 1]
						= ExecEvalExpr(fc_ex->exprstate,
									   fc_econtext,
									   &fc_newslot->tts_isnull[fc_ex->attnum - 1]);
				}

				fc_insertslot = fc_newslot;
			}
			else
			{
				/*
				 * 如果没有重写，旧表和新表的 AM 保证是相同的，因此我们可以只使用旧槽来验证新的约束等。
				 */
				fc_insertslot = fc_oldslot;
			}

			/* 现在检查可能已更改的元组上的任何约束 */
			fc_econtext->ecxt_scantuple = fc_insertslot;

			foreach(fc_l, fc_notnull_attrs)
			{
				int			fc_attn = lfirst_int(fc_l);

				if (slot_attisnull(fc_insertslot, fc_attn + 1))
				{
					Form_pg_attribute fc_attr = TupleDescAttr(fc_newTupDesc, fc_attn);

					ereport(ERROR,
							(errcode(ERRCODE_NOT_NULL_VIOLATION),
							 errmsg("column \"%s\" of relation \"%s\" contains null values",
									NameStr(fc_attr->attname),
									RelationGetRelationName(fc_oldrel)),
							 errtablecol(fc_oldrel, fc_attn + 1)));
				}
			}

			foreach(fc_l, fc_tab->constraints)
			{
				NewConstraint *fc_con = lfirst(fc_l);

				switch (fc_con->contype)
				{
					case CONSTR_CHECK:
						if (!ExecCheck(fc_con->qualstate, fc_econtext))
							ereport(ERROR,
									(errcode(ERRCODE_CHECK_VIOLATION),
									 errmsg("check constraint \"%s\" of relation \"%s\" is violated by some row",
											fc_con->name,
											RelationGetRelationName(fc_oldrel)),
									 errtableconstraint(fc_oldrel, fc_con->name)));
						break;
					case CONSTR_FOREIGN:
						/* 这里没有事情要做 */
						break;
					default:
						elog(ERROR, "unrecognized constraint type: %d",
							 (int) fc_con->contype);
				}
			}

			if (fc_partqualstate && !ExecCheck(fc_partqualstate, fc_econtext))
			{
				if (fc_tab->validate_default)
					ereport(ERROR,
							(errcode(ERRCODE_CHECK_VIOLATION),
							 errmsg("updated partition constraint for default partition \"%s\" would be violated by some row",
									RelationGetRelationName(fc_oldrel)),
							 errtable(fc_oldrel)));
				else
					ereport(ERROR,
							(errcode(ERRCODE_CHECK_VIOLATION),
							 errmsg("partition constraint of relation \"%s\" is violated by some row",
									RelationGetRelationName(fc_oldrel)),
							 errtable(fc_oldrel)));
			}

			/* 将元组写入新关系 */
			if (fc_newrel)
				table_tuple_insert(fc_newrel, fc_insertslot, fc_mycid,
								   fc_ti_options, fc_bistate);

			ResetExprContext(fc_econtext);

			CHECK_FOR_INTERRUPTS();
		}

		MemoryContextSwitchTo(fc_oldCxt);
		table_endscan(fc_scan);
		UnregisterSnapshot(fc_snapshot);

		ExecDropSingleTupleTableSlot(fc_oldslot);
		if (fc_newslot)
			ExecDropSingleTupleTableSlot(fc_newslot);
	}

	FreeExecutorState(fc_estate);

	table_close(fc_oldrel, NoLock);
	if (fc_newrel)
	{
		FreeBulkInsertState(fc_bistate);

		table_finish_bulk_insert(fc_newrel, fc_ti_options);

		table_close(fc_newrel, NoLock);
	}
}

/*
 * ATGetQueueEntry：在 ALTER TABLE 工作队列中查找或创建条目
 */
static AlteredTableInfo *
fc_ATGetQueueEntry(List **fc_wqueue, Relation fc_rel)
{
	Oid			fc_relid = RelationGetRelid(fc_rel);
	AlteredTableInfo *fc_tab;
	ListCell   *fc_ltab;

	foreach(fc_ltab, *fc_wqueue)
	{
		fc_tab = (AlteredTableInfo *) lfirst(fc_ltab);
		if (fc_tab->relid == fc_relid)
			return fc_tab;
	}

	/*
	 * 不在其中，因此添加它。请注意，我们在任何有趣的事情发生之前复制关系的现有描述符。
	 */
	fc_tab = (AlteredTableInfo *) palloc0(sizeof(AlteredTableInfo));
	fc_tab->relid = fc_relid;
	fc_tab->rel = NULL;			/* 稍后设置 */
	fc_tab->relkind = fc_rel->rd_rel->relkind;
	fc_tab->oldDesc = CreateTupleDescCopyConstr(RelationGetDescr(fc_rel));
	fc_tab->newAccessMethod = InvalidOid;
	fc_tab->newTableSpace = InvalidOid;
	fc_tab->newrelpersistence = RELPERSISTENCE_PERMANENT;
	fc_tab->chgPersistence = false;

	*fc_wqueue = lappend(*fc_wqueue, fc_tab);

	return fc_tab;
}

static const char * fc_alter_table_type_to_string(AlterTableType fc_cmdtype)
{
	switch (fc_cmdtype)
	{
		case AT_AddColumn:
		case AT_AddColumnRecurse:
		case AT_AddColumnToView:
			return "ADD COLUMN";
		case AT_ColumnDefault:
		case AT_CookedColumnDefault:
			return "ALTER COLUMN ... SET DEFAULT";
		case AT_DropNotNull:
			return "ALTER COLUMN ... DROP NOT NULL";
		case AT_SetNotNull:
			return "ALTER COLUMN ... SET NOT NULL";
		case AT_DropExpression:
			return "ALTER COLUMN ... DROP EXPRESSION";
		case AT_CheckNotNull:
			return NULL;		/* 不是实际语法 */
		case AT_SetStatistics:
			return "ALTER COLUMN ... SET STATISTICS";
		case AT_SetOptions:
			return "ALTER COLUMN ... SET";
		case AT_ResetOptions:
			return "ALTER COLUMN ... RESET";
		case AT_SetStorage:
			return "ALTER COLUMN ... SET STORAGE";
		case AT_SetCompression:
			return "ALTER COLUMN ... SET COMPRESSION";
		case AT_DropColumn:
		case AT_DropColumnRecurse:
			return "DROP COLUMN";
		case AT_AddIndex:
		case AT_ReAddIndex:
			return NULL;		/* 不是实际语法 */
		case AT_AddConstraint:
		case AT_AddConstraintRecurse:
		case AT_ReAddConstraint:
		case AT_ReAddDomainConstraint:
		case AT_AddIndexConstraint:
			return "ADD CONSTRAINT";
		case AT_AlterConstraint:
			return "ALTER CONSTRAINT";
		case AT_ValidateConstraint:
		case AT_ValidateConstraintRecurse:
			return "VALIDATE CONSTRAINT";
		case AT_DropConstraint:
		case AT_DropConstraintRecurse:
			return "DROP CONSTRAINT";
		case AT_ReAddComment:
			return NULL;		/* 不是实际语法 */
		case AT_AlterColumnType:
			return "ALTER COLUMN ... SET DATA TYPE";
		case AT_AlterColumnGenericOptions:
			return "ALTER COLUMN ... OPTIONS";
		case AT_ChangeOwner:
			return "OWNER TO";
		case AT_ClusterOn:
			return "CLUSTER ON";
		case AT_DropCluster:
			return "SET WITHOUT CLUSTER";
		case AT_SetAccessMethod:
			return "SET ACCESS METHOD";
		case AT_SetLogged:
			return "SET LOGGED";
		case AT_SetUnLogged:
			return "SET UNLOGGED";
		case AT_DropOids:
			return "SET WITHOUT OIDS";
		case AT_SetTableSpace:
			return "SET TABLESPACE";
		case AT_SetRelOptions:
			return "SET";
		case AT_ResetRelOptions:
			return "RESET";
		case AT_ReplaceRelOptions:
			return NULL;		/* 不是实际语法 */
		case AT_EnableTrig:
			return "ENABLE TRIGGER";
		case AT_EnableAlwaysTrig:
			return "ENABLE ALWAYS TRIGGER";
		case AT_EnableReplicaTrig:
			return "ENABLE REPLICA TRIGGER";
		case AT_DisableTrig:
			return "DISABLE TRIGGER";
		case AT_EnableTrigAll:
			return "ENABLE TRIGGER ALL";
		case AT_DisableTrigAll:
			return "DISABLE TRIGGER ALL";
		case AT_EnableTrigUser:
			return "ENABLE TRIGGER USER";
		case AT_DisableTrigUser:
			return "DISABLE TRIGGER USER";
		case AT_EnableRule:
			return "ENABLE RULE";
		case AT_EnableAlwaysRule:
			return "ENABLE ALWAYS RULE";
		case AT_EnableReplicaRule:
			return "ENABLE REPLICA RULE";
		case AT_DisableRule:
			return "DISABLE RULE";
		case AT_AddInherit:
			return "INHERIT";
		case AT_DropInherit:
			return "NO INHERIT";
		case AT_AddOf:
			return "OF";
		case AT_DropOf:
			return "NOT OF";
		case AT_ReplicaIdentity:
			return "REPLICA IDENTITY";
		case AT_EnableRowSecurity:
			return "ENABLE ROW SECURITY";
		case AT_DisableRowSecurity:
			return "DISABLE ROW SECURITY";
		case AT_ForceRowSecurity:
			return "FORCE ROW SECURITY";
		case AT_NoForceRowSecurity:
			return "NO FORCE ROW SECURITY";
		case AT_GenericOptions:
			return "OPTIONS";
		case AT_AttachPartition:
			return "ATTACH PARTITION";
		case AT_DetachPartition:
			return "DETACH PARTITION";
		case AT_DetachPartitionFinalize:
			return "DETACH PARTITION ... FINALIZE";
		case AT_AddIdentity:
			return "ALTER COLUMN ... ADD IDENTITY";
		case AT_SetIdentity:
			return "ALTER COLUMN ... SET";
		case AT_DropIdentity:
			return "ALTER COLUMN ... DROP IDENTITY";
		case AT_ReAddStatistics:
			return NULL;		/* 不是实际语法 */
	}

	return NULL;
}

/*
 * ATSimplePermissions
 *
 * - 确保它是一个关系（或可能是一个视图）
 * - 确保该用户是所有者
 * - 确保它不是系统表
 */
static void fc_ATSimplePermissions(AlterTableType fc_cmdtype, Relation fc_rel, int fc_allowed_targets)
{
	int			fc_actual_target;

	switch (fc_rel->rd_rel->relkind)
	{
		case RELKIND_RELATION:
		case RELKIND_PARTITIONED_TABLE:
			fc_actual_target = ATT_TABLE;
			break;
		case RELKIND_VIEW:
			fc_actual_target = ATT_VIEW;
			break;
		case RELKIND_MATVIEW:
			fc_actual_target = ATT_MATVIEW;
			break;
		case RELKIND_INDEX:
			fc_actual_target = ATT_INDEX;
			break;
		case RELKIND_PARTITIONED_INDEX:
			fc_actual_target = ATT_PARTITIONED_INDEX;
			break;
		case RELKIND_COMPOSITE_TYPE:
			fc_actual_target = ATT_COMPOSITE_TYPE;
			break;
		case RELKIND_FOREIGN_TABLE:
			fc_actual_target = ATT_FOREIGN_TABLE;
			break;
		case RELKIND_SEQUENCE:
			fc_actual_target = ATT_SEQUENCE;
			break;
		default:
			fc_actual_target = 0;
			break;
	}

	/* 错误的目标类型？ */
	if ((fc_actual_target & fc_allowed_targets) == 0)
	{
		const char *fc_action_str = fc_alter_table_type_to_string(fc_cmdtype);

		if (fc_action_str)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
			/* 翻译者：%s 是一些 SQL 关键字的组合 */
					 errmsg("ALTER action %s cannot be performed on relation \"%s\"",
							fc_action_str, RelationGetRelationName(fc_rel)),
					 errdetail_relkind_not_supported(fc_rel->rd_rel->relkind)));
		else
			/* 内部错误？ */
			elog(ERROR, "invalid ALTER action attempted on relation \"%s\"",
				 RelationGetRelationName(fc_rel));
	}

	/* 权限检查 */
	if (!pg_class_ownercheck(RelationGetRelid(fc_rel), GetUserId()))
		aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(fc_rel->rd_rel->relkind),
					   RelationGetRelationName(fc_rel));

	if (!allowSystemTableMods && IsSystemRelation(fc_rel))
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("permission denied: \"%s\" is a system catalog",
						RelationGetRelationName(fc_rel))));
}

/*
 * ATSimpleRecursion
 *
 * 简单表递归对于大多数 ALTER TABLE 操作是足够的。
 * 所有直接和间接子对象都以未指定的顺序处理。
 * 请注意，如果一个子对象通过多个继承路径从原始表继承，它将只被访问一次。
 */
static void fc_ATSimpleRecursion(List **fc_wqueue, Relation fc_rel,
				  AlterTableCmd *fc_cmd, bool fc_recurse, LOCKMODE fc_lockmode,
				  AlterTableUtilityContext *fc_context)
{
	/*
	 * 如果需要并且有（或可能有）任何子对象，则传播到子对象。
	 */
	if (fc_recurse && fc_rel->rd_rel->relhassubclass)
	{
		Oid			fc_relid = RelationGetRelid(fc_rel);
		ListCell   *fc_child;
		List	   *fc_children;

		fc_children = find_all_inheritors(fc_relid, fc_lockmode, NULL);

		/*
		 * find_all_inheritors 执行继承层次的递归搜索，
		 * 所以我们要做的就是处理它返回的列表中的所有 relids。
		 */
		foreach(fc_child, fc_children)
		{
			Oid			fc_childrelid = lfirst_oid(fc_child);
			Relation	fc_childrel;

			if (fc_childrelid == fc_relid)
				continue;
			/* find_all_inheritors 已经获得锁 */
			fc_childrel = relation_open(fc_childrelid, NoLock);
			fc_CheckAlterTableIsSafe(fc_childrel);
			fc_ATPrepCmd(fc_wqueue, fc_childrel, fc_cmd, false, true, fc_lockmode, fc_context);
			relation_close(fc_childrel, NoLock);
		}
	}
}

/*
 * 获取给定表的分区列表，以给定的锁定模式锁定它们所有，并确保它们都通过 CheckAlterTableIsSafe。
 *
 * 如果给定关系不是分区表，则此函数为无操作；
 * 特别地，如果它是遗留继承父表，则不执行任何操作。
 */
static void fc_ATCheckPartitionsNotInUse(Relation fc_rel, LOCKMODE fc_lockmode)
{
	if (fc_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
	{
		List	   *fc_inh;
		ListCell   *fc_cell;

		fc_inh = find_all_inheritors(RelationGetRelid(fc_rel), fc_lockmode, NULL);
		/* 第一个元素是父 rel；必须忽略它 */
		for_each_from(fc_cell, fc_inh, 1)
		{
			Relation	fc_childrel;

			/* find_all_inheritors 已经获得锁 */
			fc_childrel = table_open(lfirst_oid(fc_cell), NoLock);
			fc_CheckAlterTableIsSafe(fc_childrel);
			table_close(fc_childrel, NoLock);
		}
		list_free(fc_inh);
	}
}

/*
 * ATTypedTableRecursion
 *
 * 将 ALTER TYPE 操作传播到该类型的类型表。
 * 还检查 RESTRICT/CASCADE 行为。考虑到 CASCADE，也允许递归到类型表的继承子对象。
 */
static void fc_ATTypedTableRecursion(List **fc_wqueue, Relation fc_rel, AlterTableCmd *fc_cmd,
					  LOCKMODE fc_lockmode, AlterTableUtilityContext *fc_context)
{
	ListCell   *fc_child;
	List	   *fc_children;

	Assert(fc_rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE);

	fc_children = fc_find_typed_table_dependencies(fc_rel->rd_rel->reltype,
											 RelationGetRelationName(fc_rel),
											 fc_cmd->behavior);

	foreach(fc_child, fc_children)
	{
		Oid			fc_childrelid = lfirst_oid(fc_child);
		Relation	fc_childrel;

		fc_childrel = relation_open(fc_childrelid, fc_lockmode);
		fc_CheckAlterTableIsSafe(fc_childrel);
		fc_ATPrepCmd(fc_wqueue, fc_childrel, fc_cmd, true, true, fc_lockmode, fc_context);
		relation_close(fc_childrel, NoLock);
	}
}


/*
 * find_composite_type_dependencies
 *
 * 检查类型"typeOid"是否作为某个表中的列被使用
 * （可能嵌套在复合类型、数组等多个层级深处！）。
 * 最终，我们希望将检查或重写操作传播到这些表中，
 * 但目前，如果发现任何情况，请直接报错。
 *
 * 调用者应该提供行类型的关联关系，
 * 或者类型名称（不能同时提供）以便在出错信息中使用。
 *
 * 请注意，“typeOid”不一定是复合类型；它也可以是
 * 其他容器类型，例如数组或范围，或这些类型中的一个域。
 * 因此此函数的名称有些历史意义，但改动没有必要。
 *
 * 我们假设依赖于该类型的函数和视图并不是拒绝修改的理由。
 * （这真的安全吗？）
 */
void find_composite_type_dependencies(Oid fc_typeOid, Relation fc_origRelation,
								 const char *fc_origTypeName)
{
	Relation	fc_depRel;
	ScanKeyData fc_key[2];
	SysScanDesc fc_depScan;
	HeapTuple	fc_depTup;

	/* 由于这个函数递归，可能会导致栈溢出 */
	check_stack_depth();

	/*
	 * 我们扫描pg_depend以查找依赖于给定类型的事物。
	 * （我们假设可以忽略类型的refobjsubid。）
	 */
	fc_depRel = table_open(DependRelationId, AccessShareLock);

	ScanKeyInit(&fc_key[0],
				Anum_pg_depend_refclassid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(TypeRelationId));
	ScanKeyInit(&fc_key[1],
				Anum_pg_depend_refobjid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_typeOid));

	fc_depScan = systable_beginscan(fc_depRel, DependReferenceIndexId, true,
								 NULL, 2, fc_key);

	while (HeapTupleIsValid(fc_depTup = systable_getnext(fc_depScan)))
	{
		Form_pg_depend fc_pg_depend = (Form_pg_depend) GETSTRUCT(fc_depTup);
		Relation	fc_rel;
		TupleDesc	fc_tupleDesc;
		Form_pg_attribute fc_att;

		/* 检查直接依赖的类型 */
		if (fc_pg_depend->classid == TypeRelationId)
		{
			/*
			 * 这必须是一个包含给定类型的数组、域或范围，
			 * 因此递归检查该类型的使用情况。请注意，
			 * 任何错误信息将提到原始类型而不是
			 * 容器；这是故意的。
			 */
			find_composite_type_dependencies(fc_pg_depend->objid,
											 fc_origRelation, fc_origTypeName);
			continue;
		}

		/* 否则，忽略非关系的依赖项 */
		if (fc_pg_depend->classid != RelationRelationId)
			continue;

		fc_rel = relation_open(fc_pg_depend->objid, AccessShareLock);
		fc_tupleDesc = RelationGetDescr(fc_rel);

		/*
		 * 如果objsubid标识特定列，请在错误消息中引用该列。
		 * 否则，搜索是否存在该类型的用户列。
		 * （我们假设系统列永远不会是有趣的类型。）
		 * 该搜索是必要的，因为包含目标类型表达式列的索引
		 * 将仅被记录为整个关系依赖项。
		 * 如果我们没有找到该类型的列，依赖项
		 * 必须表示该类型在索引表达式中暂时引用，但未存储在磁盘上，
		 * 我们假设这是可以的，就像我们在视图中的引用一样。
		 * （也可能目标类型嵌入在某种存储在索引
		 * 列中的容器类型中，但之前的递归应该捕获这种情况。）
		 */
		if (fc_pg_depend->objsubid > 0 && fc_pg_depend->objsubid <= fc_tupleDesc->natts)
			fc_att = TupleDescAttr(fc_tupleDesc, fc_pg_depend->objsubid - 1);
		else
		{
			fc_att = NULL;
			for (int fc_attno = 1; fc_attno <= fc_tupleDesc->natts; fc_attno++)
			{
				fc_att = TupleDescAttr(fc_tupleDesc, fc_attno - 1);
				if (fc_att->atttypid == fc_typeOid && !fc_att->attisdropped)
					break;
				fc_att = NULL;
			}
			if (fc_att == NULL)
			{
				/* 没有这样的列，因此假设OK */
				relation_close(fc_rel, AccessShareLock);
				continue;
			}
		}

		/*
		 * 如果关系具有存储，肯定应该拒绝。
		 * 如果它是分区的，则也许我们不必拒绝：
		 * 如果有分区，当我们发现一个时我们会失败，
		 * 否则没有需要担心的存储数据。
		 * 但是，类型更改可能会影响关于类型是否可排序
		 * 或可哈希的结论，因此（如果它是分区列）会打破
		 * 分区规则。目前，也对分区关系拒绝。
		 */
		if (RELKIND_HAS_STORAGE(fc_rel->rd_rel->relkind) ||
			RELKIND_HAS_PARTITIONS(fc_rel->rd_rel->relkind))
		{
			if (fc_origTypeName)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("cannot alter type \"%s\" because column \"%s.%s\" uses it",
								fc_origTypeName,
								RelationGetRelationName(fc_rel),
								NameStr(fc_att->attname))));
			else if (fc_origRelation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("cannot alter type \"%s\" because column \"%s.%s\" uses it",
								RelationGetRelationName(fc_origRelation),
								RelationGetRelationName(fc_rel),
								NameStr(fc_att->attname))));
			else if (fc_origRelation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("cannot alter foreign table \"%s\" because column \"%s.%s\" uses its row type",
								RelationGetRelationName(fc_origRelation),
								RelationGetRelationName(fc_rel),
								NameStr(fc_att->attname))));
			else
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("cannot alter table \"%s\" because column \"%s.%s\" uses its row type",
								RelationGetRelationName(fc_origRelation),
								RelationGetRelationName(fc_rel),
								NameStr(fc_att->attname))));
		}
		else if (OidIsValid(fc_rel->rd_rel->reltype))
		{
			/*
			 * 视图或复合类型本身不是问题，但我们必须
			 * 递归检查其行类型的间接依赖。
			 */
			find_composite_type_dependencies(fc_rel->rd_rel->reltype,
											 fc_origRelation, fc_origTypeName);
		}

		relation_close(fc_rel, AccessShareLock);
	}

	systable_endscan(fc_depScan);

	relation_close(fc_depRel, AccessShareLock);
}


/*
 * find_typed_table_dependencies
 *
 * 检查复合类型是否被用作有类型表的类型。如果发现任何情况并且行为是RESTRICT，则中止。
 * 否则返回表的列表。
 */
static List * fc_find_typed_table_dependencies(Oid fc_typeOid, const char *fc_typeName, DropBehavior fc_behavior)
{
	Relation	fc_classRel;
	ScanKeyData fc_key[1];
	TableScanDesc fc_scan;
	HeapTuple	fc_tuple;
	List	   *fc_result = NIL;

	fc_classRel = table_open(RelationRelationId, AccessShareLock);

	ScanKeyInit(&fc_key[0],
				Anum_pg_class_reloftype,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_typeOid));

	fc_scan = table_beginscan_catalog(fc_classRel, 1, fc_key);

	while ((fc_tuple = heap_getnext(fc_scan, ForwardScanDirection)) != NULL)
	{
		Form_pg_class fc_classform = (Form_pg_class) GETSTRUCT(fc_tuple);

		if (fc_behavior == DROP_RESTRICT)
			ereport(ERROR,
					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
					 errmsg("cannot alter type \"%s\" because it is the type of a typed table",
							fc_typeName),
					 errhint("Use ALTER ... CASCADE to alter the typed tables too.")));
		else
			fc_result = lappend_oid(fc_result, fc_classform->oid);
	}

	table_endscan(fc_scan);
	table_close(fc_classRel, AccessShareLock);

	return fc_result;
}


/*
 * check_of_type
 *
 * 检查类型是否适合CREATE TABLE OF/ALTER TABLE OF。如果不合适，则抛出错误。
 * 目前，我们要求该类型是通过CREATE TYPE AS创建的。我们可以支持任何行类型，
 * 但这样做需要在DDL命令中处理许多额外的边缘情况。
 * （此外，允许领域在复合类型上使用会引发一系列关于领域的约束是否以及如何应用于派生表的问题。）
 */
void check_of_type(HeapTuple fc_typetuple)
{
	Form_pg_type fc_typ = (Form_pg_type) GETSTRUCT(fc_typetuple);
	bool		fc_typeOk = false;

	if (fc_typ->typtype == TYPTYPE_COMPOSITE)
	{
		Relation	fc_typeRelation;

		Assert(OidIsValid(fc_typ->typrelid));
		fc_typeRelation = relation_open(fc_typ->typrelid, AccessShareLock);
		fc_typeOk = (fc_typeRelation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE);

		/*
		 * 关闭父关系，但在事务提交之前保持对其的AccessShareLock。
		 * 这将防止其他人删除或ALTER类型，在有类型表创建/转换提交之前。
		 */
		relation_close(fc_typeRelation, NoLock);
	}
	if (!fc_typeOk)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("type %s is not a composite type",
						format_type_be(fc_typ->oid))));
}


/*
 * ALTER TABLE ADD COLUMN
 *
 * 向关系添加一个附加属性，假设CHECK、NOT NULL和FOREIGN KEY约束将从
 * AT_AddColumn AlterTableCmd中通过parse_utilcmd.c移除，并作为独立的
 * AlterTableCmd添加。
 *
 * ADD COLUMN不能使用普通的ALTER TABLE递归机制，因为我们必须在运行时决定
 * 是否递归，这取决于我们是实际上添加了一个列，还是仅与现有列合并。
 * （我们无法在静态预处理阶段检查这一点，因为它无法正确处理多重继承情况。）
 */
static void fc_ATPrepAddColumn(List **fc_wqueue, Relation fc_rel, bool fc_recurse, bool fc_recursing,
				bool fc_is_view, AlterTableCmd *fc_cmd, LOCKMODE fc_lockmode,
				AlterTableUtilityContext *fc_context)
{
	if (fc_rel->rd_rel->reloftype && !fc_recursing)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("cannot add column to typed table")));

	if (fc_rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
		fc_ATTypedTableRecursion(fc_wqueue, fc_rel, fc_cmd, fc_lockmode, fc_context);

	if (fc_recurse && !fc_is_view)
		fc_cmd->subtype = AT_AddColumnRecurse;
}

/*
 * 向表中添加列。返回值是父关系中新列的地址。
 *
 * cmd是按引用传递的，以便我们可以用解析转换后的副本替换它
 * （但这只有在检查IF NOT EXISTS之后才会发生）。
 */
static ObjectAddress fc_ATExecAddColumn(List **fc_wqueue, AlteredTableInfo *fc_tab, Relation fc_rel,
				AlterTableCmd **fc_cmd,
				bool fc_recurse, bool fc_recursing,
				LOCKMODE fc_lockmode, int fc_cur_pass,
				AlterTableUtilityContext *fc_context)
{
	Oid			fc_myrelid = RelationGetRelid(fc_rel);
	ColumnDef  *fc_colDef = castNode(ColumnDef, (*fc_cmd)->def);
	bool		fc_if_not_exists = (*fc_cmd)->missing_ok;
	Relation	fc_pgclass,
				fc_attrdesc;
	HeapTuple	fc_reltup;
	FormData_pg_attribute fc_attribute;
	int			fc_newattnum;
	char		fc_relkind;
	HeapTuple	fc_typeTuple;
	Oid			fc_typeOid;
	int32		fc_typmod;
	Oid			fc_collOid;
	Form_pg_type fc_tform;
	Expr	   *fc_defval;
	List	   *fc_children;
	ListCell   *fc_child;
	AlterTableCmd *fc_childcmd;
	AclResult	fc_aclresult;
	ObjectAddress fc_address;
	TupleDesc	fc_tupdesc;
	FormData_pg_attribute *fc_aattr[] = {&fc_attribute};

	/* 由于这个函数递归，可能会导致栈溢出 */
	check_stack_depth();

	/* 在顶层，权限检查是在ATPrepCmd中进行的，否则进行检查 */
	if (fc_recursing)
		fc_ATSimplePermissions((*fc_cmd)->subtype, fc_rel, ATT_TABLE | ATT_FOREIGN_TABLE);

	if (fc_rel->rd_rel->relispartition && !fc_recursing)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("cannot add column to a partition")));

	fc_attrdesc = table_open(AttributeRelationId, RowExclusiveLock);

	/*
	 * 我们是在向递归子项添加列吗？如果是，检查是否与现有的列定义合并。
	 * 如果我们合并，则必须不递归。子项将已经具有该列，递归进入它们会
	 * 打乱attinhcount。
	 */
	if (fc_colDef->inhcount > 0)
	{
		HeapTuple	fc_tuple;

		/* 子项是否已经有这个名称的列？ */
		fc_tuple = SearchSysCacheCopyAttName(fc_myrelid, fc_colDef->colname);
		if (HeapTupleIsValid(fc_tuple))
		{
			Form_pg_attribute fc_childatt = (Form_pg_attribute) GETSTRUCT(fc_tuple);
			Oid			fc_ctypeId;
			int32		fc_ctypmod;
			Oid			fc_ccollid;

			/* 子列必须在类型、typmod和排序规则上匹配 */
			typenameTypeIdAndMod(NULL, fc_colDef->typeName, &fc_ctypeId, &fc_ctypmod);
			if (fc_ctypeId != fc_childatt->atttypid ||
				fc_ctypmod != fc_childatt->atttypmod)
				ereport(ERROR,
						(errcode(ERRCODE_DATATYPE_MISMATCH),
						 errmsg("child table \"%s\" has different type for column \"%s\"",
								RelationGetRelationName(fc_rel), fc_colDef->colname)));
			fc_ccollid = GetColumnDefCollation(NULL, fc_colDef, fc_ctypeId);
			if (fc_ccollid != fc_childatt->attcollation)
				ereport(ERROR,
						(errcode(ERRCODE_COLLATION_MISMATCH),
						 errmsg("child table \"%s\" has different collation for column \"%s\"",
								RelationGetRelationName(fc_rel), fc_colDef->colname),
						 errdetail("\"%s\" versus \"%s\"",
								   get_collation_name(fc_ccollid),
								   get_collation_name(fc_childatt->attcollation))));

			/* 增加现有子项的inhcount */
			fc_childatt->attinhcount++;
			CatalogTupleUpdate(fc_attrdesc, &fc_tuple->t_self, fc_tuple);

			heap_freetuple(fc_tuple);

			/* 通知用户关于合并的信息 */
			ereport(NOTICE,
					(errmsg("merging definition of column \"%s\" for child \"%s\"",
							fc_colDef->colname, RelationGetRelationName(fc_rel))));

			table_close(fc_attrdesc, RowExclusiveLock);

			/* 使子列变化可见 */
			CommandCounterIncrement();

			return InvalidObjectAddress;
		}
	}

	/* 如果名称已存在且if_not_exists为真，则跳过 */
	if (!fc_check_for_column_name_collision(fc_rel, fc_colDef->colname, fc_if_not_exists))
	{
		table_close(fc_attrdesc, RowExclusiveLock);
		return InvalidObjectAddress;
	}

	/*
	 * 好的，我们需要添加列，所以继续进行解析转换。
	 * 这可能会导致排队，甚至立即执行附属操作（例如创建唯一索引）；
	 * 所以在进行 if_not_exists 检查之前我们不能这样做。
	 *
	 * 在递归时，命令已经被转换，我们不需要再次进行转换。
	 * 此外，如果没有给定上下文，我们无法进行转换。 （目前这种情况仅发生在 AT_AddColumnToView；我们期望 view.c 传递给我们一个不需要处理的 ColumnDef。）
	 */
	if (fc_context != NULL && !fc_recursing)
	{
		*fc_cmd = fc_ATParseTransformCmd(fc_wqueue, fc_tab, fc_rel, *fc_cmd, fc_recurse, fc_lockmode,
								   fc_cur_pass, fc_context);
		Assert(*fc_cmd != NULL);
		fc_colDef = castNode(ColumnDef, (*fc_cmd)->def);
	}

	/*
	 * 如果表有子表，则无法添加身份列，因为身份不继承。
	 * （单独添加列和身份是可以的。）
	 */
	if (fc_colDef->identity &&
		fc_recurse &&
		find_inheritance_children(fc_myrelid, NoLock) != NIL)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
				 errmsg("cannot recursively add identity column to table that has child tables")));

	fc_pgclass = table_open(RelationRelationId, RowExclusiveLock);

	fc_reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(fc_myrelid));
	if (!HeapTupleIsValid(fc_reltup))
		elog(ERROR, "cache lookup failed for relation %u", fc_myrelid);
	fc_relkind = ((Form_pg_class) GETSTRUCT(fc_reltup))->relkind;

	/* 确定新属性的编号 */
	fc_newattnum = ((Form_pg_class) GETSTRUCT(fc_reltup))->relnatts + 1;
	if (fc_newattnum > MaxHeapAttributeNumber)
		ereport(ERROR,
				(errcode(ERRCODE_TOO_MANY_COLUMNS),
				 errmsg("tables can have at most %d columns",
						MaxHeapAttributeNumber)));

	fc_typeTuple = typenameType(NULL, fc_colDef->typeName, &fc_typmod);
	fc_tform = (Form_pg_type) GETSTRUCT(fc_typeTuple);
	fc_typeOid = fc_tform->oid;

	fc_aclresult = pg_type_aclcheck(fc_typeOid, GetUserId(), ACL_USAGE);
	if (fc_aclresult != ACLCHECK_OK)
		aclcheck_error_type(fc_aclresult, fc_typeOid);

	fc_collOid = GetColumnDefCollation(NULL, fc_colDef, fc_typeOid);

	/* 确保数据类型对于列是合法的 */
	CheckAttributeType(fc_colDef->colname, fc_typeOid, fc_collOid,
					   list_make1_oid(fc_rel->rd_rel->reltype),
					   0);

	/*
	 * 构建新属性的 pg_attribute 条目。 （可变长度字段
	 * 由 InsertPgAttributeTuples() 处理。）
	 */
	fc_attribute.attrelid = fc_myrelid;
	namestrcpy(&(fc_attribute.attname), fc_colDef->colname);
	fc_attribute.atttypid = fc_typeOid;
	fc_attribute.attstattarget = (fc_newattnum > 0) ? -1 : 0;
	fc_attribute.attlen = fc_tform->typlen;
	fc_attribute.attnum = fc_newattnum;
	fc_attribute.attndims = list_length(fc_colDef->typeName->arrayBounds);
	fc_attribute.atttypmod = fc_typmod;
	fc_attribute.attbyval = fc_tform->typbyval;
	fc_attribute.attalign = fc_tform->typalign;
	fc_attribute.attstorage = fc_tform->typstorage;
	fc_attribute.attcompression = fc_GetAttributeCompression(fc_typeOid,
													   fc_colDef->compression);
	fc_attribute.attnotnull = fc_colDef->is_not_null;
	fc_attribute.atthasdef = false;
	fc_attribute.atthasmissing = false;
	fc_attribute.attidentity = fc_colDef->identity;
	fc_attribute.attgenerated = fc_colDef->generated;
	fc_attribute.attisdropped = false;
	fc_attribute.attislocal = fc_colDef->is_local;
	fc_attribute.attinhcount = fc_colDef->inhcount;
	fc_attribute.attcollation = fc_collOid;

	ReleaseSysCache(fc_typeTuple);

	fc_tupdesc = CreateTupleDesc(lengthof(fc_aattr), (FormData_pg_attribute **) &fc_aattr);

	InsertPgAttributeTuples(fc_attrdesc, fc_tupdesc, fc_myrelid, NULL, NULL);

	table_close(fc_attrdesc, RowExclusiveLock);

	/*
	 * 根据需要更新 pg_class 元组
	 */
	((Form_pg_class) GETSTRUCT(fc_reltup))->relnatts = fc_newattnum;

	CatalogTupleUpdate(fc_pgclass, &fc_reltup->t_self, fc_reltup);

	heap_freetuple(fc_reltup);

	/* 新属性的创建后钩子 */
	InvokeObjectPostCreateHook(RelationRelationId, fc_myrelid, fc_newattnum);

	table_close(fc_pgclass, RowExclusiveLock);

	/* 使属性的目录条目可见 */
	CommandCounterIncrement();

	/*
	 * 如果有的话，将 DEFAULT 存储在目录中
	 */
	if (fc_colDef->raw_default)
	{
		RawColumnDefault *fc_rawEnt;

		fc_rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
		fc_rawEnt->attnum = fc_attribute.attnum;
		fc_rawEnt->raw_default = copyObject(fc_colDef->raw_default);

		/*
		 * 试图通过在堆之外存储指定的 DEFAULT 值来跳过完整的表重写。
		 * 如果无法应用该优化，则可能在 AddRelationNewConstraints 中禁用该功能。
		 */
		fc_rawEnt->missingMode = (!fc_colDef->generated);

		fc_rawEnt->generated = fc_colDef->generated;

		/*
		 * 此函数旨在用于 CREATE TABLE，因此它处理一个
		 * _列表_ 的默认值，但我们只处理一个。
		 */
		AddRelationNewConstraints(fc_rel, list_make1(fc_rawEnt), NIL,
								  false, true, false, NULL);

		/* 使附加目录更改可见 */
		CommandCounterIncrement();

		/*
		 * 请求缺失值的操作成功吗？如果不成功，我们将不得不进行重写
		 */
		if (!fc_rawEnt->missingMode)
			fc_tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
	}

	/*
	 * 告诉阶段 3 填充默认表达式（如果有的话）。
	 *
	 * 如果没有默认值，阶段 3 不需要做任何事情，因为
	 * 这实际上意味着默认值是 NULL。堆元组访问
	 * 例程总是检查 attnum 是否大于元组中的属性数量，如果是，则返回
	 * NULL，因此在没有修改元组数据的情况下，我们将在新列中实现
	 * NULL 值的效果。
	 *
	 * 当新列是域类型时，会发生一个例外：域
	 * 可能有 NOT NULL 约束，或间接拒绝 null 的检查约束。
	 * 如果存在任何域约束，则我们构造
	 * 一个明确的 NULL 默认值，将通过
	 * CoerceToDomain 处理。(这有点效率低下，因为它导致
	 * 重写表，而我们实际上并不需要这样做，但目前
	 * 域处理的设计并没有提供任何简单的方法来更直接地检查
	 * 约束。)
	 *
	 * 注意：我们使用 build_column_default，而不仅仅是 AddRelationNewConstraints
	 * 返回的烹饪默认值，以便在数据类型的默认值适用时发生正确的事情。
	 *
	 * 注意：这似乎应该发生在阶段 2 的末尾，以便
	 * 考虑到后续子命令的效果。
	 * 不过，我们这样做是故意的。新列应该根据
	 * ADD COLUMN 子命令中的说明填充，以便效果与
	 * 如果这个子命令单独运行并且后续子命令在新的 ALTER TABLE 命令中发出时
	 * 完全相同。
	 *
	 * 对于没有存储的关系，我们可以完全跳过这一步，因为阶段 3
	 * 肯定不会碰到它们。系统属性也没有
	 * 有趣的默认值。
	 */
	if (RELKIND_HAS_STORAGE(fc_relkind) && fc_attribute.attnum > 0)
	{
		/*
		 * 对于身份列，我们不能使用 build_column_default()
		 * 因为序列所有权尚未设置。因此手动处理。
		 */
		if (fc_colDef->identity)
		{
			NextValueExpr *fc_nve = makeNode(NextValueExpr);

			fc_nve->seqid = RangeVarGetRelid(fc_colDef->identitySequence, NoLock, false);
			fc_nve->typeId = fc_typeOid;

			fc_defval = (Expr *) fc_nve;

			/* 对于身份列必须进行重写 */
			fc_tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
		}
		else
			fc_defval = (Expr *) build_column_default(fc_rel, fc_attribute.attnum);

		if (!fc_defval && DomainHasConstraints(fc_typeOid))
		{
			Oid			fc_baseTypeId;
			int32		fc_baseTypeMod;
			Oid			fc_baseTypeColl;

			fc_baseTypeMod = fc_typmod;
			fc_baseTypeId = getBaseTypeAndTypmod(fc_typeOid, &fc_baseTypeMod);
			fc_baseTypeColl = get_typcollation(fc_baseTypeId);
			fc_defval = (Expr *) makeNullConst(fc_baseTypeId, fc_baseTypeMod, fc_baseTypeColl);
			fc_defval = (Expr *) coerce_to_target_type(NULL,
													(Node *) fc_defval,
													fc_baseTypeId,
													fc_typeOid,
													fc_typmod,
													COERCION_ASSIGNMENT,
													COERCE_IMPLICIT_CAST,
													-1);
			if (fc_defval == NULL) /* 不应该发生 */
				elog(ERROR, "failed to coerce base type to domain");
		}

		if (fc_defval)
		{
			NewColumnValue *fc_newval;

			fc_newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue));
			fc_newval->attnum = fc_attribute.attnum;
			fc_newval->expr = expression_planner(fc_defval);
			fc_newval->is_generated = (fc_colDef->generated != '\0');

			fc_tab->newvals = lappend(fc_tab->newvals, fc_newval);
		}

		if (DomainHasConstraints(fc_typeOid))
			fc_tab->rewrite |= AT_REWRITE_DEFAULT_VAL;

		if (!TupleDescAttr(fc_rel->rd_att, fc_attribute.attnum - 1)->atthasmissing)
		{
			/*
			 * 如果新列为 NOT NULL，且没有缺失值，
			 * 告诉阶段 3 需要检查 NULL。
			 */
			fc_tab->verify_new_notnull |= fc_colDef->is_not_null;
		}
	}

	/*
	 * 为新列添加所需的依赖条目。
	 */
	fc_add_column_datatype_dependency(fc_myrelid, fc_newattnum, fc_attribute.atttypid);
	fc_add_column_collation_dependency(fc_myrelid, fc_newattnum, fc_attribute.attcollation);

	/*
	 * 根据需要向子级传播。与大多数其他 ALTER
	 * 例程不同，我们必须一次进行一层递归；我们不能
	 * 使用 find_all_inheritors 一次性完成。
	 */
	fc_children =
		find_inheritance_children(RelationGetRelid(fc_rel), fc_lockmode);

	/*
	 * 如果我们被告知不进行递归，那么最好没有任何子表；
	 * 否则，添加将使它们步调不一致。
	 */
	if (fc_children && !fc_recurse)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
				 errmsg("column must be added to child tables too")));

	/* 子级应该将列视为单继承 */
	if (!fc_recursing)
	{
		fc_childcmd = copyObject(*fc_cmd);
		fc_colDef = castNode(ColumnDef, fc_childcmd->def);
		fc_colDef->inhcount = 1;
		fc_colDef->is_local = false;
	}
	else
		fc_childcmd = *fc_cmd;		/* 无需再次复制 */

	foreach(fc_child, fc_children)
	{
		Oid			fc_childrelid = lfirst_oid(fc_child);
		Relation	fc_childrel;
		AlteredTableInfo *fc_childtab;

		/* find_inheritance_children 已经获得锁定 */
		fc_childrel = table_open(fc_childrelid, NoLock);
		fc_CheckAlterTableIsSafe(fc_childrel);

		/* 查找或创建该表的工作队列条目 */
		fc_childtab = fc_ATGetQueueEntry(fc_wqueue, fc_childrel);

		/* 递归到子级；返回值被忽略 */
		fc_ATExecAddColumn(fc_wqueue, fc_childtab, fc_childrel,
						&fc_childcmd, fc_recurse, true,
						fc_lockmode, fc_cur_pass, fc_context);

		table_close(fc_childrel, NoLock);
	}

	ObjectAddressSubSet(fc_address, RelationRelationId, fc_myrelid, fc_newattnum);
	return fc_address;
}

/*
 * 如果新列或重命名列将与现有
 * 列的名称冲突，并且 if_not_exists 为 false，则引发错误，否则不执行任何操作。
 */
static bool fc_check_for_column_name_collision(Relation fc_rel, const char *fc_colname,
								bool fc_if_not_exists)
{
	HeapTuple	fc_attTuple;
	int			fc_attnum;

	/*
	 * 该测试故意不考虑被丢弃的列，因为如果尝试添加一个与已丢弃列名称匹配的列，无论如何都会失败。
	 */
	fc_attTuple = SearchSysCache2(ATTNAME,
							   ObjectIdGetDatum(RelationGetRelid(fc_rel)),
							   PointerGetDatum(fc_colname));
	if (!HeapTupleIsValid(fc_attTuple))
		return true;

	fc_attnum = ((Form_pg_attribute) GETSTRUCT(fc_attTuple))->attnum;
	ReleaseSysCache(fc_attTuple);

	/*
	 * 对于与系统列名称冲突的情况，我们抛出不同的错误信息，因为这些列通常不显示，用户可能会对冲突的原因感到困惑。
	 */
	if (fc_attnum <= 0)
		ereport(ERROR,
				(errcode(ERRCODE_DUPLICATE_COLUMN),
				 errmsg("column name \"%s\" conflicts with a system column name",
						fc_colname)));
	else
	{
		if (fc_if_not_exists)
		{
			ereport(NOTICE,
					(errcode(ERRCODE_DUPLICATE_COLUMN),
					 errmsg("column \"%s\" of relation \"%s\" already exists, skipping",
							fc_colname, RelationGetRelationName(fc_rel))));
			return false;
		}

		ereport(ERROR,
				(errcode(ERRCODE_DUPLICATE_COLUMN),
				 errmsg("column \"%s\" of relation \"%s\" already exists",
						fc_colname, RelationGetRelationName(fc_rel))));
	}

	return true;
}

/*
 * 安装列对其数据类型的依赖关系。
 */
static void fc_add_column_datatype_dependency(Oid fc_relid, int32 fc_attnum, Oid fc_typid)
{
	ObjectAddress fc_myself,
				fc_referenced;

	fc_myself.classId = RelationRelationId;
	fc_myself.objectId = fc_relid;
	fc_myself.objectSubId = fc_attnum;
	fc_referenced.classId = TypeRelationId;
	fc_referenced.objectId = fc_typid;
	fc_referenced.objectSubId = 0;
	recordDependencyOn(&fc_myself, &fc_referenced, DEPENDENCY_NORMAL);
}

/*
 * 安装列对其排序规则的依赖关系。
 */
static void fc_add_column_collation_dependency(Oid fc_relid, int32 fc_attnum, Oid fc_collid)
{
	ObjectAddress fc_myself,
				fc_referenced;

	/* 我们知道默认排序规则是固定的，因此不必记录它 */
	if (OidIsValid(fc_collid) && fc_collid != DEFAULT_COLLATION_OID)
	{
		fc_myself.classId = RelationRelationId;
		fc_myself.objectId = fc_relid;
		fc_myself.objectSubId = fc_attnum;
		fc_referenced.classId = CollationRelationId;
		fc_referenced.objectId = fc_collid;
		fc_referenced.objectSubId = 0;
		recordDependencyOn(&fc_myself, &fc_referenced, DEPENDENCY_NORMAL);
	}
}

/*
 * 修改表列为非空
 */

static void fc_ATPrepDropNotNull(Relation fc_rel, bool fc_recurse, bool fc_recursing)
{
	/*
	 * 如果父表是分区表，类似于检查约束，我们不支持在存在分区时删除非空约束。
	 */
	if (fc_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
	{
		PartitionDesc fc_partdesc = RelationGetPartitionDesc(fc_rel, true);

		Assert(fc_partdesc != NULL);
		if (fc_partdesc->nparts > 0 && !fc_recurse && !fc_recursing)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
					 errmsg("cannot remove constraint from only the partitioned table when partitions exist"),
					 errhint("Do not specify the ONLY keyword.")));
	}
}

/*
 * 返回已修改列的地址。如果列已经是可空的，则返回无效对象地址。
 */
static ObjectAddress fc_ATExecDropNotNull(Relation fc_rel, const char *fc_colName, LOCKMODE fc_lockmode)
{
	HeapTuple	fc_tuple;
	Form_pg_attribute fc_attTup;
	AttrNumber	fc_attnum;
	Relation	fc_attr_rel;
	List	   *fc_indexoidlist;
	ListCell   *fc_indexoidscan;
	ObjectAddress fc_address;

	/*
	 * 查找属性
	 */
	fc_attr_rel = table_open(AttributeRelationId, RowExclusiveLock);

	fc_tuple = SearchSysCacheCopyAttName(RelationGetRelid(fc_rel), fc_colName);
	if (!HeapTupleIsValid(fc_tuple))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_COLUMN),
				 errmsg("column \"%s\" of relation \"%s\" does not exist",
						fc_colName, RelationGetRelationName(fc_rel))));
	fc_attTup = (Form_pg_attribute) GETSTRUCT(fc_tuple);
	fc_attnum = fc_attTup->attnum;

	/* 防止他们修改系统属性 */
	if (fc_attnum <= 0)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot alter system column \"%s\"",
						fc_colName)));

	if (fc_attTup->attidentity)
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("column \"%s\" of relation \"%s\" is an identity column",
						fc_colName, RelationGetRelationName(fc_rel))));

	/*
	 * 检查该属性不在主键中或不在用作复制标识的索引中。
	 *
	 * 注意：即使主键索引无效，我们也会抛出错误。
	 */

	/* 遍历关系上的所有索引 */
	fc_indexoidlist = RelationGetIndexList(fc_rel);

	foreach(fc_indexoidscan, fc_indexoidlist)
	{
		Oid			fc_indexoid = lfirst_oid(fc_indexoidscan);
		HeapTuple	fc_indexTuple;
		Form_pg_index fc_indexStruct;
		int			fc_i;

		fc_indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(fc_indexoid));
		if (!HeapTupleIsValid(fc_indexTuple))
			elog(ERROR, "cache lookup failed for index %u", fc_indexoid);
		fc_indexStruct = (Form_pg_index) GETSTRUCT(fc_indexTuple);

		/*
		 * 如果索引不是主键或用作复制标识的索引，跳过检查。
		 */
		if (fc_indexStruct->indisprimary || fc_indexStruct->indisreplident)
		{
			/*
			 * 遍历主键或用作复制标识的索引中的每个属性，查看它是否与待修改的属性匹配。
			 */
			for (fc_i = 0; fc_i < fc_indexStruct->indnkeyatts; fc_i++)
			{
				if (fc_indexStruct->indkey.values[fc_i] == fc_attnum)
				{
					if (fc_indexStruct->indisprimary)
						ereport(ERROR,
								(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
								 errmsg("column \"%s\" is in a primary key",
										fc_colName)));
					else
						ereport(ERROR,
								(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
								 errmsg("column \"%s\" is in index used as replica identity",
										fc_colName)));
				}
			}
		}

		ReleaseSysCache(fc_indexTuple);
	}

	list_free(fc_indexoidlist);

	/* 如果关系是分区，如果父级具有相同的约束，则不应删除非空约束 */
	if (fc_rel->rd_rel->relispartition)
	{
		Oid			fc_parentId = get_partition_parent(RelationGetRelid(fc_rel), false);
		Relation	fc_parent = table_open(fc_parentId, AccessShareLock);
		TupleDesc	fc_tupDesc = RelationGetDescr(fc_parent);
		AttrNumber	fc_parent_attnum;

		fc_parent_attnum = get_attnum(fc_parentId, fc_colName);
		if (TupleDescAttr(fc_tupDesc, fc_parent_attnum - 1)->attnotnull)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
					 errmsg("column \"%s\" is marked NOT NULL in parent table",
							fc_colName)));
		table_close(fc_parent, AccessShareLock);
	}

	/*
	 * 好吧，实际上执行目录更改……如果需要
	 */
	if (fc_attTup->attnotnull)
	{
		fc_attTup->attnotnull = false;

		CatalogTupleUpdate(fc_attr_rel, &fc_tuple->t_self, fc_tuple);

		ObjectAddressSubSet(fc_address, RelationRelationId,
							RelationGetRelid(fc_rel), fc_attnum);
	}
	else
		fc_address = InvalidObjectAddress;

	InvokeObjectPostAlterHook(RelationRelationId,
							  RelationGetRelid(fc_rel), fc_attnum);

	table_close(fc_attr_rel, RowExclusiveLock);

	return fc_address;
}

/*
 * 修改表列为非空
 */

static void fc_ATPrepSetNotNull(List **fc_wqueue, Relation fc_rel,
				 AlterTableCmd *fc_cmd, bool fc_recurse, bool fc_recursing,
				 LOCKMODE fc_lockmode, AlterTableUtilityContext *fc_context)
{
	/*
	 * 如果我们已经在递归，就没有任何事情可做；ATSimpleRecursion的最上层调用已经访问了所有子级。
	 */
	if (fc_recursing)
		return;

	/*
	 * 如果目标列已经标记为非空，我们可以跳过对子项的递归，因为它们的列应该也已经标记为非空。然而，除非关系有一些子项，否则在这里检查是没有意义的；否则我们可以等到执行时再检查。（如果确实有子项，然而，这可以节省不必要的逐个子项锁定。在一些并行恢复场景中，这大大提高了并发性。）
	 *
	 * 不幸的是，我们只能将此优化应用于分区表，因为传统继承不强制要求当父项为非空时子项也为非空。（这是一个应该在某天得到修复的错误。）
	 */
	if (fc_rel->rd_rel->relhassubclass &&
		fc_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
	{
		HeapTuple	fc_tuple;
		bool		fc_attnotnull;

		fc_tuple = SearchSysCacheAttName(RelationGetRelid(fc_rel), fc_cmd->name);

		/* 如果名称不正确，现在可以抛出错误 */
		if (!HeapTupleIsValid(fc_tuple))
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_COLUMN),
					 errmsg("column \"%s\" of relation \"%s\" does not exist",
							fc_cmd->name, RelationGetRelationName(fc_rel))));

		fc_attnotnull = ((Form_pg_attribute) GETSTRUCT(fc_tuple))->attnotnull;
		ReleaseSysCache(fc_tuple);
		if (fc_attnotnull)
			return;
	}

	/*
	 * 如果我们在分区表上有仅修改表...设置非空，则对每个子项应用修改表...检查非空。否则，使用正常的递归逻辑。
	 */
	if (fc_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
		!fc_recurse)
	{
		AlterTableCmd *fc_newcmd = makeNode(AlterTableCmd);

		fc_newcmd->subtype = AT_CheckNotNull;
		fc_newcmd->name = pstrdup(fc_cmd->name);
		fc_ATSimpleRecursion(fc_wqueue, fc_rel, fc_newcmd, true, fc_lockmode, fc_context);
	}
	else
		fc_ATSimpleRecursion(fc_wqueue, fc_rel, fc_cmd, fc_recurse, fc_lockmode, fc_context);
}

/*
 * 返回已修改列的地址。如果列已经是非空的，则返回无效对象地址。
 */
static ObjectAddress fc_ATExecSetNotNull(AlteredTableInfo *fc_tab, Relation fc_rel,
				 const char *fc_colName, LOCKMODE fc_lockmode)
{
	HeapTuple	fc_tuple;
	AttrNumber	fc_attnum;
	Relation	fc_attr_rel;
	ObjectAddress fc_address;

	/*
	 * 查找属性
	 */
	fc_attr_rel = table_open(AttributeRelationId, RowExclusiveLock);

	fc_tuple = SearchSysCacheCopyAttName(RelationGetRelid(fc_rel), fc_colName);

	if (!HeapTupleIsValid(fc_tuple))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_COLUMN),
				 errmsg("column \"%s\" of relation \"%s\" does not exist",
						fc_colName, RelationGetRelationName(fc_rel))));

	fc_attnum = ((Form_pg_attribute) GETSTRUCT(fc_tuple))->attnum;

	/* 防止他们修改系统属性 */
	if (fc_attnum <= 0)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot alter system column \"%s\"",
						fc_colName)));

	/*
	 * 好吧，实际上执行目录更改……如果需要
	 */
	if (!((Form_pg_attribute) GETSTRUCT(fc_tuple))->attnotnull)
	{
		((Form_pg_attribute) GETSTRUCT(fc_tuple))->attnotnull = true;

		CatalogTupleUpdate(fc_attr_rel, &fc_tuple->t_self, fc_tuple);

		/*
		 * 通常情况下，阶段3必须确保在被设置为NOT NULL的列中不存在NULL；
		 * 然而，如果我们可以找到一个约束证明这一点，那么我们可以跳过这一检查。
		 * 如果我们已经发现必须验证其他NOT NULL约束，就不必费心去寻找了。
		 */
		if (!fc_tab->verify_new_notnull &&
			!fc_NotNullImpliedByRelConstraints(fc_rel, (Form_pg_attribute) GETSTRUCT(fc_tuple)))
		{
			/* 告诉阶段3它需要测试该约束 */
			fc_tab->verify_new_notnull = true;
		}

		ObjectAddressSubSet(fc_address, RelationRelationId,
							RelationGetRelid(fc_rel), fc_attnum);
	}
	else
		fc_address = InvalidObjectAddress;

	InvokeObjectPostAlterHook(RelationRelationId,
							  RelationGetRelid(fc_rel), fc_attnum);

	table_close(fc_attr_rel, RowExclusiveLock);

	return fc_address;
}

/*
 * ALTER TABLE ALTER COLUMN CHECK NOT NULL
 *
 * 这在语法中不存在，但如果用户在分区表上写了ALTER TABLE ONLY ... SET NOT NULL，
 * 或者试图在其上创建主键（这内部会创建AT_SetNotNull在分区表上），
 * 我们会生成AT_CheckNotNull命令针对分区。
 * 此类命令不允许我们实际修改任何分区，但如果分区已经正确标记，我们希望允许它通过。
 *
 * 将来，这可能需要调整子表的状态，可能通过增加attnotnull约束的继承计数。
 * 目前我们只需要检查标志是否存在。
 */
static void fc_ATExecCheckNotNull(AlteredTableInfo *fc_tab, Relation fc_rel,
				   const char *fc_colName, LOCKMODE fc_lockmode)
{
	HeapTuple	fc_tuple;

	fc_tuple = SearchSysCacheAttName(RelationGetRelid(fc_rel), fc_colName);

	if (!HeapTupleIsValid(fc_tuple))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_COLUMN),
				 errmsg("column \"%s\" of relation \"%s\" does not exist",
						fc_colName, RelationGetRelationName(fc_rel))));

	if (!((Form_pg_attribute) GETSTRUCT(fc_tuple))->attnotnull)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
				 errmsg("constraint must be added to child tables too"),
				 errdetail("Column \"%s\" of relation \"%s\" is not already NOT NULL.",
						   fc_colName, RelationGetRelationName(fc_rel)),
				 errhint("Do not specify the ONLY keyword.")));

	ReleaseSysCache(fc_tuple);
}

/*
 * NotNullImpliedByRelConstraints
 *		关系的现有约束是否暗示给定属性为NOT NULL？
 */
static bool fc_NotNullImpliedByRelConstraints(Relation fc_rel, Form_pg_attribute fc_attr)
{
	NullTest   *fc_nnulltest = makeNode(NullTest);

	fc_nnulltest->arg = (Expr *) makeVar(1,
									  fc_attr->attnum,
									  fc_attr->atttypid,
									  fc_attr->atttypmod,
									  fc_attr->attcollation,
									  0);
	fc_nnulltest->nulltesttype = IS_NOT_NULL;

	/*
	 * argisrow = false即使对复合列也是正确的，因为
	 * attnotnull在这种情况下并不表示SQL规格的IS NOT NULL测试，只是IS DISTINCT FROM NULL。
	 */
	fc_nnulltest->argisrow = false;
	fc_nnulltest->location = -1;

	if (fc_ConstraintImpliedByRelConstraint(fc_rel, list_make1(fc_nnulltest), NIL))
	{
		ereport(DEBUG1,
				(errmsg_internal("existing constraints on column \"%s.%s\" are sufficient to prove that it does not contain nulls",
								 RelationGetRelationName(fc_rel), NameStr(fc_attr->attname))));
		return true;
	}

	return false;
}

/*
 * ALTER TABLE ALTER COLUMN SET/DROP DEFAULT
 *
 * 返回受影响列的地址。
 */
static ObjectAddress fc_ATExecColumnDefault(Relation fc_rel, const char *fc_colName,
					Node *fc_newDefault, LOCKMODE fc_lockmode)
{
	TupleDesc	fc_tupdesc = RelationGetDescr(fc_rel);
	AttrNumber	fc_attnum;
	ObjectAddress fc_address;

	/*
	 * 获取属性的编号
	 */
	fc_attnum = get_attnum(RelationGetRelid(fc_rel), fc_colName);
	if (fc_attnum == InvalidAttrNumber)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_COLUMN),
				 errmsg("column \"%s\" of relation \"%s\" does not exist",
						fc_colName, RelationGetRelationName(fc_rel))));

	/* 防止他们修改系统属性 */
	if (fc_attnum <= 0)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot alter system column \"%s\"",
						fc_colName)));

	if (TupleDescAttr(fc_tupdesc, fc_attnum - 1)->attidentity)
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("column \"%s\" of relation \"%s\" is an identity column",
						fc_colName, RelationGetRelationName(fc_rel)),
				 fc_newDefault ? 0 : errhint("Use ALTER TABLE ... ALTER COLUMN ... DROP IDENTITY instead.")));

	if (TupleDescAttr(fc_tupdesc, fc_attnum - 1)->attgenerated)
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("column \"%s\" of relation \"%s\" is a generated column",
						fc_colName, RelationGetRelationName(fc_rel)),
				 fc_newDefault || TupleDescAttr(fc_tupdesc, fc_attnum - 1)->attgenerated != ATTRIBUTE_GENERATED_STORED ? 0 :
				 errhint("Use ALTER TABLE ... ALTER COLUMN ... DROP EXPRESSION instead.")));

	/*
	 * 删除该列的任何旧默认值。我们在这里使用RESTRICT以确保安全，
	 * 但目前我们不期望有任何东西依赖于默认值。
	 *
	 * 我们将删除现有默认值视为内部操作，当它是添加新默认值的准备时，
	 * 但当用户要求删除时，我们视为用户发起的操作。
	 */
	RemoveAttrDefault(RelationGetRelid(fc_rel), fc_attnum, DROP_RESTRICT, false,
					  fc_newDefault != NULL);

	if (fc_newDefault)
	{
		/* 设置默认值 */
		RawColumnDefault *fc_rawEnt;

		fc_rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
		fc_rawEnt->attnum = fc_attnum;
		fc_rawEnt->raw_default = fc_newDefault;
		fc_rawEnt->missingMode = false;
		fc_rawEnt->generated = '\0';

		/*
		 * 此函数旨在用于 CREATE TABLE，因此它处理一个
		 * _列表_ 的默认值，但我们只处理一个。
		 */
		AddRelationNewConstraints(fc_rel, list_make1(fc_rawEnt), NIL,
								  false, true, false, NULL);
	}

	ObjectAddressSubSet(fc_address, RelationRelationId,
						RelationGetRelid(fc_rel), fc_attnum);
	return fc_address;
}

/*
 * 添加一个预先配置的默认表达式。
 *
 * 返回受影响列的地址。
 */
static ObjectAddress fc_ATExecCookedColumnDefault(Relation fc_rel, AttrNumber fc_attnum,
						  Node *fc_newDefault)
{
	ObjectAddress fc_address;

	/* 我们假设不需要检查 */

	/*
	 * 删除该列的任何旧默认值。我们在这里使用RESTRICT以确保安全，
	 * 但目前我们不期望有任何东西依赖于默认值。（在一般情况下，无论如何不可能存在默认值，
	 * 但在将LIKE与继承结合时是可能的。）
	 */
	RemoveAttrDefault(RelationGetRelid(fc_rel), fc_attnum, DROP_RESTRICT, false,
					  true);

	(void) StoreAttrDefault(fc_rel, fc_attnum, fc_newDefault, true, false);

	ObjectAddressSubSet(fc_address, RelationRelationId,
						RelationGetRelid(fc_rel), fc_attnum);
	return fc_address;
}

/*
 * ALTER TABLE ALTER COLUMN ADD IDENTITY
 *
 * 返回受影响列的地址。
 */
static ObjectAddress fc_ATExecAddIdentity(Relation fc_rel, const char *fc_colName,
				  Node *fc_def, LOCKMODE fc_lockmode)
{
	Relation	fc_attrelation;
	HeapTuple	fc_tuple;
	Form_pg_attribute fc_attTup;
	AttrNumber	fc_attnum;
	ObjectAddress fc_address;
	ColumnDef  *fc_cdef = castNode(ColumnDef, fc_def);

	fc_attrelation = table_open(AttributeRelationId, RowExclusiveLock);

	fc_tuple = SearchSysCacheCopyAttName(RelationGetRelid(fc_rel), fc_colName);
	if (!HeapTupleIsValid(fc_tuple))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_COLUMN),
				 errmsg("column \"%s\" of relation \"%s\" does not exist",
						fc_colName, RelationGetRelationName(fc_rel))));
	fc_attTup = (Form_pg_attribute) GETSTRUCT(fc_tuple);
	fc_attnum = fc_attTup->attnum;

	/* 不能更改系统属性 */
	if (fc_attnum <= 0)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot alter system column \"%s\"",
						fc_colName)));

	/*
	 * 创建一个列作为标识意味着 NOT NULL，因此将标识
	 * 添加到一个不是 NOT NULL 的现有列会创建一个
	 * 无法在没有扭曲的情况下复现的状态。
	 */
	if (!fc_attTup->attnotnull)
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("column \"%s\" of relation \"%s\" must be declared NOT NULL before identity can be added",
						fc_colName, RelationGetRelationName(fc_rel))));

	if (fc_attTup->attidentity)
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("column \"%s\" of relation \"%s\" is already an identity column",
						fc_colName, RelationGetRelationName(fc_rel))));

	if (fc_attTup->atthasdef)
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("column \"%s\" of relation \"%s\" already has a default value",
						fc_colName, RelationGetRelationName(fc_rel))));

	fc_attTup->attidentity = fc_cdef->identity;
	CatalogTupleUpdate(fc_attrelation, &fc_tuple->t_self, fc_tuple);

	InvokeObjectPostAlterHook(RelationRelationId,
							  RelationGetRelid(fc_rel),
							  fc_attTup->attnum);
	ObjectAddressSubSet(fc_address, RelationRelationId,
						RelationGetRelid(fc_rel), fc_attnum);
	heap_freetuple(fc_tuple);

	table_close(fc_attrelation, RowExclusiveLock);

	return fc_address;
}

/*
 * ALTER TABLE ALTER COLUMN SET { GENERATED 或序列选项 }
 *
 * 返回受影响列的地址。
 */
static ObjectAddress fc_ATExecSetIdentity(Relation fc_rel, const char *fc_colName, Node *fc_def, LOCKMODE fc_lockmode)
{
	ListCell   *fc_option;
	DefElem    *fc_generatedEl = NULL;
	HeapTuple	fc_tuple;
	Form_pg_attribute fc_attTup;
	AttrNumber	fc_attnum;
	Relation	fc_attrelation;
	ObjectAddress fc_address;

	foreach(fc_option, castNode(List, fc_def))
	{
		DefElem    *fc_defel = lfirst_node(DefElem, fc_option);

		if (strcmp(fc_defel->defname, "generated") == 0)
		{
			if (fc_generatedEl)
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("conflicting or redundant options")));
			fc_generatedEl = fc_defel;
		}
		else
			elog(ERROR, "option \"%s\" not recognized",
				 fc_defel->defname);
	}

	/*
	 * 即使这里没有任何需要更改的，我们也会运行所有检查。
	 * 之后会有一个依赖于所有内容都在的 ALTER SEQUENCE。
	 */

	fc_attrelation = table_open(AttributeRelationId, RowExclusiveLock);
	fc_tuple = SearchSysCacheCopyAttName(RelationGetRelid(fc_rel), fc_colName);
	if (!HeapTupleIsValid(fc_tuple))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_COLUMN),
				 errmsg("column \"%s\" of relation \"%s\" does not exist",
						fc_colName, RelationGetRelationName(fc_rel))));

	fc_attTup = (Form_pg_attribute) GETSTRUCT(fc_tuple);
	fc_attnum = fc_attTup->attnum;

	if (fc_attnum <= 0)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot alter system column \"%s\"",
						fc_colName)));

	if (!fc_attTup->attidentity)
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("column \"%s\" of relation \"%s\" is not an identity column",
						fc_colName, RelationGetRelationName(fc_rel))));

	if (fc_generatedEl)
	{
		fc_attTup->attidentity = defGetInt32(fc_generatedEl);
		CatalogTupleUpdate(fc_attrelation, &fc_tuple->t_self, fc_tuple);

		InvokeObjectPostAlterHook(RelationRelationId,
								  RelationGetRelid(fc_rel),
								  fc_attTup->attnum);
		ObjectAddressSubSet(fc_address, RelationRelationId,
							RelationGetRelid(fc_rel), fc_attnum);
	}
	else
		fc_address = InvalidObjectAddress;

	heap_freetuple(fc_tuple);
	table_close(fc_attrelation, RowExclusiveLock);

	return fc_address;
}

/*
 * ALTER TABLE ALTER COLUMN DROP IDENTITY
 *
 * 返回受影响列的地址。
 */
static ObjectAddress fc_ATExecDropIdentity(Relation fc_rel, const char *fc_colName, bool fc_missing_ok, LOCKMODE fc_lockmode)
{
	HeapTuple	fc_tuple;
	Form_pg_attribute fc_attTup;
	AttrNumber	fc_attnum;
	Relation	fc_attrelation;
	ObjectAddress fc_address;
	Oid			fc_seqid;
	ObjectAddress fc_seqaddress;

	fc_attrelation = table_open(AttributeRelationId, RowExclusiveLock);
	fc_tuple = SearchSysCacheCopyAttName(RelationGetRelid(fc_rel), fc_colName);
	if (!HeapTupleIsValid(fc_tuple))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_COLUMN),
				 errmsg("column \"%s\" of relation \"%s\" does not exist",
						fc_colName, RelationGetRelationName(fc_rel))));

	fc_attTup = (Form_pg_attribute) GETSTRUCT(fc_tuple);
	fc_attnum = fc_attTup->attnum;

	if (fc_attnum <= 0)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot alter system column \"%s\"",
						fc_colName)));

	if (!fc_attTup->attidentity)
	{
		if (!fc_missing_ok)
			ereport(ERROR,
					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
					 errmsg("column \"%s\" of relation \"%s\" is not an identity column",
							fc_colName, RelationGetRelationName(fc_rel))));
		else
		{
			ereport(NOTICE,
					(errmsg("column \"%s\" of relation \"%s\" is not an identity column, skipping",
							fc_colName, RelationGetRelationName(fc_rel))));
			heap_freetuple(fc_tuple);
			table_close(fc_attrelation, RowExclusiveLock);
			return InvalidObjectAddress;
		}
	}

	fc_attTup->attidentity = '\0';
	CatalogTupleUpdate(fc_attrelation, &fc_tuple->t_self, fc_tuple);

	InvokeObjectPostAlterHook(RelationRelationId,
							  RelationGetRelid(fc_rel),
							  fc_attTup->attnum);
	ObjectAddressSubSet(fc_address, RelationRelationId,
						RelationGetRelid(fc_rel), fc_attnum);
	heap_freetuple(fc_tuple);

	table_close(fc_attrelation, RowExclusiveLock);

	/* 删除内部序列 */
	fc_seqid = getIdentitySequence(RelationGetRelid(fc_rel), fc_attnum, false);
	deleteDependencyRecordsForClass(RelationRelationId, fc_seqid,
									RelationRelationId, DEPENDENCY_INTERNAL);
	CommandCounterIncrement();
	fc_seqaddress.classId = RelationRelationId;
	fc_seqaddress.objectId = fc_seqid;
	fc_seqaddress.objectSubId = 0;
	performDeletion(&fc_seqaddress, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);

	return fc_address;
}

/*
 * ALTER TABLE ALTER COLUMN DROP EXPRESSION
 */
static void fc_ATPrepDropExpression(Relation fc_rel, AlterTableCmd *fc_cmd, bool fc_recurse, bool fc_recursing, LOCKMODE fc_lockmode)
{
	/*
	 * 仅当存在子表时拒绝。我们可以实现这个，但它
	 * 有点复杂。GENERATED 子句必须附加到列
	 * 定义上，无法像 DEFAULT 一样后续添加，因此如果子表
	 * 有一个父表没有的生成表达式，子列必然会是一个 attlocal 列。
	 * 因此，要在这里实现 ONLY，我们需要额外的代码来更新直接子表的
	 * attislocal，稍微类似于 DROP COLUMN 的方法，以便结果状态
	 * 可以正确地转储和恢复。
	 */
	if (!fc_recurse &&
		find_inheritance_children(RelationGetRelid(fc_rel), fc_lockmode))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("ALTER TABLE / DROP EXPRESSION must be applied to child tables too")));

	/*
	 * 无法从继承列中删除生成表达式。
	 */
	if (!fc_recursing)
	{
		HeapTuple	fc_tuple;
		Form_pg_attribute fc_attTup;

		fc_tuple = SearchSysCacheCopyAttName(RelationGetRelid(fc_rel), fc_cmd->name);
		if (!HeapTupleIsValid(fc_tuple))
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_COLUMN),
					 errmsg("column \"%s\" of relation \"%s\" does not exist",
							fc_cmd->name, RelationGetRelationName(fc_rel))));

		fc_attTup = (Form_pg_attribute) GETSTRUCT(fc_tuple);

		if (fc_attTup->attinhcount > 0)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
					 errmsg("cannot drop generation expression from inherited column")));
	}
}

/*
 * 返回受影响列的地址。
 */
static ObjectAddress fc_ATExecDropExpression(Relation fc_rel, const char *fc_colName, bool fc_missing_ok, LOCKMODE fc_lockmode)
{
	HeapTuple	fc_tuple;
	Form_pg_attribute fc_attTup;
	AttrNumber	fc_attnum;
	Relation	fc_attrelation;
	Oid			fc_attrdefoid;
	ObjectAddress fc_address;

	fc_attrelation = table_open(AttributeRelationId, RowExclusiveLock);
	fc_tuple = SearchSysCacheCopyAttName(RelationGetRelid(fc_rel), fc_colName);
	if (!HeapTupleIsValid(fc_tuple))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_COLUMN),
				 errmsg("column \"%s\" of relation \"%s\" does not exist",
						fc_colName, RelationGetRelationName(fc_rel))));

	fc_attTup = (Form_pg_attribute) GETSTRUCT(fc_tuple);
	fc_attnum = fc_attTup->attnum;

	if (fc_attnum <= 0)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot alter system column \"%s\"",
						fc_colName)));

	if (fc_attTup->attgenerated != ATTRIBUTE_GENERATED_STORED)
	{
		if (!fc_missing_ok)
			ereport(ERROR,
					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
					 errmsg("column \"%s\" of relation \"%s\" is not a stored generated column",
							fc_colName, RelationGetRelationName(fc_rel))));
		else
		{
			ereport(NOTICE,
					(errmsg("column \"%s\" of relation \"%s\" is not a stored generated column, skipping",
							fc_colName, RelationGetRelationName(fc_rel))));
			heap_freetuple(fc_tuple);
			table_close(fc_attrelation, RowExclusiveLock);
			return InvalidObjectAddress;
		}
	}

	/*
	 * 将列标记为不再生成。（atthasdef 标志也需要
	 * 清除，但 RemoveAttrDefault 会处理这个问题。）
	 */
	fc_attTup->attgenerated = '\0';
	CatalogTupleUpdate(fc_attrelation, &fc_tuple->t_self, fc_tuple);

	InvokeObjectPostAlterHook(RelationRelationId,
							  RelationGetRelid(fc_rel),
							  fc_attnum);
	heap_freetuple(fc_tuple);

	table_close(fc_attrelation, RowExclusiveLock);

	/*
	 * 删除 GENERATED 表达式的依赖记录，特别是
	 * 它对列的 INTERNAL 依赖，否则会导致
	 * dependency.c 拒绝执行删除。
	 */
	fc_attrdefoid = GetAttrDefaultOid(RelationGetRelid(fc_rel), fc_attnum);
	if (!OidIsValid(fc_attrdefoid))
		elog(ERROR, "could not find attrdef tuple for relation %u attnum %d",
			 RelationGetRelid(fc_rel), fc_attnum);
	(void) deleteDependencyRecordsFor(AttrDefaultRelationId, fc_attrdefoid, false);

	/* 使上述更改可见 */
	CommandCounterIncrement();

	/*
	 * 移除 GENERATED 表达式本身。我们在这里使用 RESTRICT
	 * 以确保安全，但目前我们不希望任何东西依赖于
	 * 默认值。
	 */
	RemoveAttrDefault(RelationGetRelid(fc_rel), fc_attnum, DROP_RESTRICT,
					  false, false);

	ObjectAddressSubSet(fc_address, RelationRelationId,
						RelationGetRelid(fc_rel), fc_attnum);
	return fc_address;
}

/*
 * ALTER TABLE ALTER COLUMN SET STATISTICS
 *
 * 返回值是修改后列的地址
 */
static ObjectAddress fc_ATExecSetStatistics(Relation fc_rel, const char *fc_colName, int16 fc_colNum, Node *fc_newValue, LOCKMODE fc_lockmode)
{
	int			fc_newtarget;
	Relation	fc_attrelation;
	HeapTuple	fc_tuple;
	Form_pg_attribute fc_attrtuple;
	AttrNumber	fc_attnum;
	ObjectAddress fc_address;

	/*
	 * 我们只允许通过数字引用索引列，因为如果后续删除列，
	 * 表列编号可能会存在间隙。
	 */
	if (fc_rel->rd_rel->relkind != RELKIND_INDEX &&
		fc_rel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX &&
		!fc_colName)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot refer to non-index column by number")));

	Assert(IsA(fc_newValue, Integer));
	fc_newtarget = intVal(fc_newValue);

	/*
	 * 限制目标在合理范围内
	 */
	if (fc_newtarget < -1)
	{
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("statistics target %d is too low",
						fc_newtarget)));
	}
	else if (fc_newtarget > 10000)
	{
		fc_newtarget = 10000;
		ereport(WARNING,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("lowering statistics target to %d",
						fc_newtarget)));
	}

	fc_attrelation = table_open(AttributeRelationId, RowExclusiveLock);

	if (fc_colName)
	{
		fc_tuple = SearchSysCacheCopyAttName(RelationGetRelid(fc_rel), fc_colName);

		if (!HeapTupleIsValid(fc_tuple))
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_COLUMN),
					 errmsg("column \"%s\" of relation \"%s\" does not exist",
							fc_colName, RelationGetRelationName(fc_rel))));
	}
	else
	{
		fc_tuple = SearchSysCacheCopyAttNum(RelationGetRelid(fc_rel), fc_colNum);

		if (!HeapTupleIsValid(fc_tuple))
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_COLUMN),
					 errmsg("column number %d of relation \"%s\" does not exist",
							fc_colNum, RelationGetRelationName(fc_rel))));
	}

	fc_attrtuple = (Form_pg_attribute) GETSTRUCT(fc_tuple);

	fc_attnum = fc_attrtuple->attnum;
	if (fc_attnum <= 0)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot alter system column \"%s\"",
						fc_colName)));

	if (fc_rel->rd_rel->relkind == RELKIND_INDEX ||
		fc_rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
	{
		if (fc_attnum > fc_rel->rd_index->indnkeyatts)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("cannot alter statistics on included column \"%s\" of index \"%s\"",
							NameStr(fc_attrtuple->attname), RelationGetRelationName(fc_rel))));
		else if (fc_rel->rd_index->indkey.values[fc_attnum - 1] != 0)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("cannot alter statistics on non-expression column \"%s\" of index \"%s\"",
							NameStr(fc_attrtuple->attname), RelationGetRelationName(fc_rel)),
					 errhint("Alter statistics on table column instead.")));
	}

	fc_attrtuple->attstattarget = fc_newtarget;

	CatalogTupleUpdate(fc_attrelation, &fc_tuple->t_self, fc_tuple);

	InvokeObjectPostAlterHook(RelationRelationId,
							  RelationGetRelid(fc_rel),
							  fc_attrtuple->attnum);
	ObjectAddressSubSet(fc_address, RelationRelationId,
						RelationGetRelid(fc_rel), fc_attnum);
	heap_freetuple(fc_tuple);

	table_close(fc_attrelation, RowExclusiveLock);

	return fc_address;
}

/*
 * 返回值是修改后列的地址
 */
static ObjectAddress fc_ATExecSetOptions(Relation fc_rel, const char *fc_colName, Node *fc_options,
				 bool fc_isReset, LOCKMODE fc_lockmode)
{
	Relation	fc_attrelation;
	HeapTuple	fc_tuple,
				fc_newtuple;
	Form_pg_attribute fc_attrtuple;
	AttrNumber	fc_attnum;
	Datum		fc_datum,
				fc_newOptions;
	bool		fc_isnull;
	ObjectAddress fc_address;
	Datum		fc_repl_val[Natts_pg_attribute];
	bool		fc_repl_null[Natts_pg_attribute];
	bool		fc_repl_repl[Natts_pg_attribute];

	fc_attrelation = table_open(AttributeRelationId, RowExclusiveLock);

	fc_tuple = SearchSysCacheAttName(RelationGetRelid(fc_rel), fc_colName);

	if (!HeapTupleIsValid(fc_tuple))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_COLUMN),
				 errmsg("column \"%s\" of relation \"%s\" does not exist",
						fc_colName, RelationGetRelationName(fc_rel))));
	fc_attrtuple = (Form_pg_attribute) GETSTRUCT(fc_tuple);

	fc_attnum = fc_attrtuple->attnum;
	if (fc_attnum <= 0)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot alter system column \"%s\"",
						fc_colName)));

	/* 生成新的提议 attoptions（文本数组） */
	fc_datum = SysCacheGetAttr(ATTNAME, fc_tuple, Anum_pg_attribute_attoptions,
							&fc_isnull);
	fc_newOptions = transformRelOptions(fc_isnull ? (Datum) 0 : fc_datum,
									 castNode(List, fc_options), NULL, NULL,
									 false, fc_isReset);
	/* 验证新选项 */
	(void) attribute_reloptions(fc_newOptions, true);

	/* 构建新元组。 */
	memset(fc_repl_null, false, sizeof(fc_repl_null));
	memset(fc_repl_repl, false, sizeof(fc_repl_repl));
	if (fc_newOptions != (Datum) 0)
		fc_repl_val[Anum_pg_attribute_attoptions - 1] = fc_newOptions;
	else
		fc_repl_null[Anum_pg_attribute_attoptions - 1] = true;
	fc_repl_repl[Anum_pg_attribute_attoptions - 1] = true;
	fc_newtuple = heap_modify_tuple(fc_tuple, RelationGetDescr(fc_attrelation),
								 fc_repl_val, fc_repl_null, fc_repl_repl);

	/* 更新系统目录。 */
	CatalogTupleUpdate(fc_attrelation, &fc_newtuple->t_self, fc_newtuple);

	InvokeObjectPostAlterHook(RelationRelationId,
							  RelationGetRelid(fc_rel),
							  fc_attrtuple->attnum);
	ObjectAddressSubSet(fc_address, RelationRelationId,
						RelationGetRelid(fc_rel), fc_attnum);

	heap_freetuple(fc_newtuple);

	ReleaseSysCache(fc_tuple);

	table_close(fc_attrelation, RowExclusiveLock);

	return fc_address;
}

/*
 * ATExecSetStorage 和 ATExecSetCompression 的辅助函数
 *
 * 为与指定表列关联的索引列设置 attstorage 和/或 attcompression 字段。
 */
static void fc_SetIndexStorageProperties(Relation fc_rel, Relation fc_attrelation,
						  AttrNumber fc_attnum,
						  bool fc_setstorage, char fc_newstorage,
						  bool fc_setcompression, char fc_newcompression,
						  LOCKMODE fc_lockmode)
{
	ListCell   *fc_lc;

	foreach(fc_lc, RelationGetIndexList(fc_rel))
	{
		Oid			fc_indexoid = lfirst_oid(fc_lc);
		Relation	fc_indrel;
		AttrNumber	fc_indattnum = 0;
		HeapTuple	fc_tuple;

		fc_indrel = index_open(fc_indexoid, fc_lockmode);

		for (int fc_i = 0; fc_i < fc_indrel->rd_index->indnatts; fc_i++)
		{
			if (fc_indrel->rd_index->indkey.values[fc_i] == fc_attnum)
			{
				fc_indattnum = fc_i + 1;
				break;
			}
		}

		if (fc_indattnum == 0)
		{
			index_close(fc_indrel, fc_lockmode);
			continue;
		}

		fc_tuple = SearchSysCacheCopyAttNum(RelationGetRelid(fc_indrel), fc_indattnum);

		if (HeapTupleIsValid(fc_tuple))
		{
			Form_pg_attribute fc_attrtuple = (Form_pg_attribute) GETSTRUCT(fc_tuple);

			if (fc_setstorage)
				fc_attrtuple->attstorage = fc_newstorage;

			if (fc_setcompression)
				fc_attrtuple->attcompression = fc_newcompression;

			CatalogTupleUpdate(fc_attrelation, &fc_tuple->t_self, fc_tuple);

			InvokeObjectPostAlterHook(RelationRelationId,
									  RelationGetRelid(fc_rel),
									  fc_attrtuple->attnum);

			heap_freetuple(fc_tuple);
		}

		index_close(fc_indrel, fc_lockmode);
	}
}

/*
 * ALTER TABLE ALTER COLUMN SET STORAGE
 *
 * 返回值是修改后列的地址
 */
static ObjectAddress fc_ATExecSetStorage(Relation fc_rel, const char *fc_colName, Node *fc_newValue, LOCKMODE fc_lockmode)
{
	char	   *fc_storagemode;
	char		fc_newstorage;
	Relation	fc_attrelation;
	HeapTuple	fc_tuple;
	Form_pg_attribute fc_attrtuple;
	AttrNumber	fc_attnum;
	ObjectAddress fc_address;

	Assert(IsA(fc_newValue, String));
	fc_storagemode = strVal(fc_newValue);

	if (pg_strcasecmp(fc_storagemode, "plain") == 0)
		fc_newstorage = TYPSTORAGE_PLAIN;
	else if (pg_strcasecmp(fc_storagemode, "external") == 0)
		fc_newstorage = TYPSTORAGE_EXTERNAL;
	else if (pg_strcasecmp(fc_storagemode, "extended") == 0)
		fc_newstorage = TYPSTORAGE_EXTENDED;
	else if (pg_strcasecmp(fc_storagemode, "main") == 0)
		fc_newstorage = TYPSTORAGE_MAIN;
	else
	{
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("invalid storage type \"%s\"",
						fc_storagemode)));
		fc_newstorage = 0;			/* 保持编译器安静 */
	}

	fc_attrelation = table_open(AttributeRelationId, RowExclusiveLock);

	fc_tuple = SearchSysCacheCopyAttName(RelationGetRelid(fc_rel), fc_colName);

	if (!HeapTupleIsValid(fc_tuple))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_COLUMN),
				 errmsg("column \"%s\" of relation \"%s\" does not exist",
						fc_colName, RelationGetRelationName(fc_rel))));
	fc_attrtuple = (Form_pg_attribute) GETSTRUCT(fc_tuple);

	fc_attnum = fc_attrtuple->attnum;
	if (fc_attnum <= 0)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot alter system column \"%s\"",
						fc_colName)));

	/*
	 * 安全检查：除非列数据类型
	 * 是 TOAST-aware，否则不允许啤酒存储模式。
	 */
	if (fc_newstorage == TYPSTORAGE_PLAIN || TypeIsToastable(fc_attrtuple->atttypid))
		fc_attrtuple->attstorage = fc_newstorage;
	else
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("column data type %s can only have storage PLAIN",
						format_type_be(fc_attrtuple->atttypid))));

	CatalogTupleUpdate(fc_attrelation, &fc_tuple->t_self, fc_tuple);

	InvokeObjectPostAlterHook(RelationRelationId,
							  RelationGetRelid(fc_rel),
							  fc_attrtuple->attnum);

	heap_freetuple(fc_tuple);

	/*
	 * 将更改应用于索引（仅适用于简单索引列，
	 * 与 index.c ConstructTupleDescriptor() 的行为一致）。
	 */
	fc_SetIndexStorageProperties(fc_rel, fc_attrelation, fc_attnum,
							  true, fc_newstorage,
							  false, 0,
							  fc_lockmode);

	table_close(fc_attrelation, RowExclusiveLock);

	ObjectAddressSubSet(fc_address, RelationRelationId,
						RelationGetRelid(fc_rel), fc_attnum);
	return fc_address;
}


/*
 * ALTER TABLE DROP COLUMN
 *
 * DROP COLUMN 不能使用正常的 ALTER TABLE 递归机制，
 * 因为我们必须在运行时决定是否递归，取决于 attinhcount 是否归零。
 * （我们无法在静态预处理阶段检查这一点，因为它无法正确处理多重继承情况。）
 */
static void fc_ATPrepDropColumn(List **fc_wqueue, Relation fc_rel, bool fc_recurse, bool fc_recursing,
				 AlterTableCmd *fc_cmd, LOCKMODE fc_lockmode,
				 AlterTableUtilityContext *fc_context)
{
	if (fc_rel->rd_rel->reloftype && !fc_recursing)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("cannot drop column from typed table")));

	if (fc_rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
		fc_ATTypedTableRecursion(fc_wqueue, fc_rel, fc_cmd, fc_lockmode, fc_context);

	if (fc_recurse)
		fc_cmd->subtype = AT_DropColumnRecurse;
}

/*
 * 从关系 'rel' 中删除列 'colName' 并返回已删除列的地址。
 * 如果有，列也会从关系的继承子关系中删除（或标记为不再继承）。
 *
 * 在对子关系的递归调用中，如果要删除列，不能直接删除列，而是将其对象地址
 * 添加到 'addrs' 中，在这样的调用中必须是非空的。
 * 所有列将在所有孩子被递归检查后同时删除。
 */
static ObjectAddress fc_ATExecDropColumn(List **fc_wqueue, Relation fc_rel, const char *fc_colName,
				 DropBehavior fc_behavior,
				 bool fc_recurse, bool fc_recursing,
				 bool fc_missing_ok, LOCKMODE fc_lockmode,
				 ObjectAddresses *fc_addrs)
{
	HeapTuple	fc_tuple;
	Form_pg_attribute fc_targetatt;
	AttrNumber	fc_attnum;
	List	   *fc_children;
	ObjectAddress fc_object;
	bool		fc_is_expr;

	/* 在顶层，权限检查是在ATPrepCmd中进行的，否则进行检查 */
	if (fc_recursing)
		fc_ATSimplePermissions(AT_DropColumn, fc_rel, ATT_TABLE | ATT_FOREIGN_TABLE);

	/* 在第一次调用时初始化 addrs */
	Assert(!fc_recursing || fc_addrs != NULL);

	/* 由于这个函数递归，可能会导致栈溢出 */
	check_stack_depth();

	if (!fc_recursing)
		fc_addrs = new_object_addresses();

	/*
	 * 获取属性的编号
	 */
	fc_tuple = SearchSysCacheAttName(RelationGetRelid(fc_rel), fc_colName);
	if (!HeapTupleIsValid(fc_tuple))
	{
		if (!fc_missing_ok)
		{
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_COLUMN),
					 errmsg("column \"%s\" of relation \"%s\" does not exist",
							fc_colName, RelationGetRelationName(fc_rel))));
		}
		else
		{
			ereport(NOTICE,
					(errmsg("column \"%s\" of relation \"%s\" does not exist, skipping",
							fc_colName, RelationGetRelationName(fc_rel))));
			return InvalidObjectAddress;
		}
	}
	fc_targetatt = (Form_pg_attribute) GETSTRUCT(fc_tuple);

	fc_attnum = fc_targetatt->attnum;

	/* 不能删除系统属性 */
	if (fc_attnum <= 0)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot drop system column \"%s\"",
						fc_colName)));

	/*
	 * 除非递归（可能是由于父列的删除），否则不要删除继承列
	 */
	if (fc_targetatt->attinhcount > 0 && !fc_recursing)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
				 errmsg("cannot drop inherited column \"%s\"",
						fc_colName)));

	/*
	 * 也不要删除用于分区键的列。（如果允许这样做，
	 * 则键列的依赖关系将导致整个表级联删除，这肯定不是用户所期望的。）
	 */
	if (has_partition_attrs(fc_rel,
							bms_make_singleton(fc_attnum - FirstLowInvalidHeapAttributeNumber),
							&fc_is_expr))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
				 errmsg("cannot drop column \"%s\" because it is part of the partition key of relation \"%s\"",
						fc_colName, RelationGetRelationName(fc_rel))));

	ReleaseSysCache(fc_tuple);

	/*
	 * 根据需要向子级传播。与大多数其他 ALTER
	 * 例程不同，我们必须一次进行一层递归；我们不能
	 * 使用 find_all_inheritors 一次性完成。
	 */
	fc_children =
		find_inheritance_children(RelationGetRelid(fc_rel), fc_lockmode);

	if (fc_children)
	{
		Relation	fc_attr_rel;
		ListCell   *fc_child;

		/*
		 * 如果是分区表，则必须从分区中删除该列。
		 */
		if (fc_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !fc_recurse)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
					 errmsg("cannot drop column from only the partitioned table when partitions exist"),
					 errhint("Do not specify the ONLY keyword.")));

		fc_attr_rel = table_open(AttributeRelationId, RowExclusiveLock);
		foreach(fc_child, fc_children)
		{
			Oid			fc_childrelid = lfirst_oid(fc_child);
			Relation	fc_childrel;
			Form_pg_attribute fc_childatt;

			/* find_inheritance_children 已经获得锁定 */
			fc_childrel = table_open(fc_childrelid, NoLock);
			fc_CheckAlterTableIsSafe(fc_childrel);

			fc_tuple = SearchSysCacheCopyAttName(fc_childrelid, fc_colName);
			if (!HeapTupleIsValid(fc_tuple))	/* 不应该发生 */
				elog(ERROR, "cache lookup failed for attribute \"%s\" of relation %u",
					 fc_colName, fc_childrelid);
			fc_childatt = (Form_pg_attribute) GETSTRUCT(fc_tuple);

			if (fc_childatt->attinhcount <= 0) /* 不应该发生 */
				elog(ERROR, "relation %u has non-inherited attribute \"%s\"",
					 fc_childrelid, fc_colName);

			if (fc_recurse)
			{
				/*
				 * 如果子列有其他定义源，只需
				 * 减少其继承计数；如果没有，则递归删除它。
				 */
				if (fc_childatt->attinhcount == 1 && !fc_childatt->attislocal)
				{
					/* 该子列也是时候删除了 */
					fc_ATExecDropColumn(fc_wqueue, fc_childrel, fc_colName,
									 fc_behavior, true, true,
									 false, fc_lockmode, fc_addrs);
				}
				else
				{
					/* 子列必须在我的删除中存活 */
					fc_childatt->attinhcount--;

					CatalogTupleUpdate(fc_attr_rel, &fc_tuple->t_self, fc_tuple);

					/* 使更新可见 */
					CommandCounterIncrement();
				}
			}
			else
			{
				/*
				 * 如果我们被告知仅在此表中删除（不递归），
				 * 我们需要将继承者的属性标记为局部定义而非继承。
				 */
				fc_childatt->attinhcount--;
				fc_childatt->attislocal = true;

				CatalogTupleUpdate(fc_attr_rel, &fc_tuple->t_self, fc_tuple);

				/* 使更新可见 */
				CommandCounterIncrement();
			}

			heap_freetuple(fc_tuple);

			table_close(fc_childrel, NoLock);
		}
		table_close(fc_attr_rel, RowExclusiveLock);
	}

	/* 添加要删除的对象 */
	fc_object.classId = RelationRelationId;
	fc_object.objectId = RelationGetRelid(fc_rel);
	fc_object.objectSubId = fc_attnum;
	add_exact_object_address(&fc_object, fc_addrs);

	if (!fc_recursing)
	{
		/* 递归已结束，删除所有收集的内容 */
		performMultipleDeletions(fc_addrs, fc_behavior, 0);
		free_object_addresses(fc_addrs);
	}

	return fc_object;
}

/*
 * ALTER TABLE 添加索引
 *
 * 在语法中没有这样的命令，但 parse_utilcmd.c 将 UNIQUE 和 PRIMARY KEY 约束转换为 AT_AddIndex 子命令。这让我们可以在 ALTER 操作进行到适当时机时调度索引的创建。
 *
 * 返回值是新索引的地址。
 */
static ObjectAddress fc_ATExecAddIndex(AlteredTableInfo *fc_tab, Relation fc_rel,
			   IndexStmt *fc_stmt, bool fc_is_rebuild, LOCKMODE fc_lockmode)
{
	bool		fc_check_rights;
	bool		fc_skip_build;
	bool		fc_quiet;
	ObjectAddress fc_address;

	Assert(IsA(fc_stmt, IndexStmt));
	Assert(!fc_stmt->concurrent);

	/* IndexStmt 已经经过 transformIndexStmt 处理 */
	Assert(fc_stmt->transformed);

	/* 重建现有索引时抑制模式权限检查 */
	fc_check_rights = !fc_is_rebuild;
	/* 如果第 3 阶段会执行此操作或我们正在重用旧的索引，则跳过索引构建 */
	fc_skip_build = fc_tab->rewrite > 0 || OidIsValid(fc_stmt->oldNode);
	/* 重建现有索引时抑制通知 */
	fc_quiet = fc_is_rebuild;

	fc_address = DefineIndex(RelationGetRelid(fc_rel),
						  fc_stmt,
						  InvalidOid,	/* 没有预定义的 OID */
						  InvalidOid,	/* 没有父索引 */
						  InvalidOid,	/* 没有父约束 */
						  true, /* is_alter_table */
						  fc_check_rights,
						  false,	/* check_not_in_use - 我们已经做过了 */
						  fc_skip_build,
						  fc_quiet);

	/*
	 * 如果 TryReuseIndex() 为我们存储了一个 relfilenode，我们就用它用于新索引，而不是重新构建。恢复相关字段。
	 * 这可能会在两个字段中存储 InvalidSubTransactionId，这样 relcache.c 将假定它可以重建 relcache 条目。因此，在使目录行对任何重建可见的 CCI 之后执行此操作。
	 * 旧版本索引的 DROP 操作会在提交时计划删除存储，因此取消该待处理删除。
	 */
	if (OidIsValid(fc_stmt->oldNode))
	{
		Relation	fc_irel = index_open(fc_address.objectId, NoLock);

		fc_irel->rd_createSubid = fc_stmt->oldCreateSubid;
		fc_irel->rd_firstRelfilenodeSubid = fc_stmt->oldFirstRelfilenodeSubid;
		RelationPreserveStorage(fc_irel->rd_node, true);
		index_close(fc_irel, NoLock);
	}

	return fc_address;
}

/*
 * ALTER TABLE 添加统计信息
 *
 * 在语法中没有这样的命令，但我们内部使用此命令添加 AT_ReAddStatistics 子命令以在表列类型更改后重建扩展统计信息。
 */
static ObjectAddress fc_ATExecAddStatistics(AlteredTableInfo *fc_tab, Relation fc_rel,
					CreateStatsStmt *fc_stmt, bool fc_is_rebuild, LOCKMODE fc_lockmode)
{
	ObjectAddress fc_address;

	Assert(IsA(fc_stmt, CreateStatsStmt));

	/* CreateStatsStmt 已经经过 transformStatsStmt 处理 */
	Assert(fc_stmt->transformed);

	fc_address = CreateStatistics(fc_stmt);

	return fc_address;
}

/*
 * ALTER TABLE 使用索引添加约束
 *
 * 返回新约束的地址。
 */
static ObjectAddress fc_ATExecAddIndexConstraint(AlteredTableInfo *fc_tab, Relation fc_rel,
						 IndexStmt *fc_stmt, LOCKMODE fc_lockmode)
{
	Oid			fc_index_oid = fc_stmt->indexOid;
	Relation	fc_indexRel;
	char	   *fc_indexName;
	IndexInfo  *fc_indexInfo;
	char	   *fc_constraintName;
	char		fc_constraintType;
	ObjectAddress fc_address;
	bits16		fc_flags;

	Assert(IsA(fc_stmt, IndexStmt));
	Assert(OidIsValid(fc_index_oid));
	Assert(fc_stmt->isconstraint);

	/*
	 * 在分区表上执行此操作并不是一个简单的功能，因此现在先搁置。
	 */
	if (fc_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("ALTER TABLE / ADD CONSTRAINT USING INDEX is not supported on partitioned tables")));

	fc_indexRel = index_open(fc_index_oid, AccessShareLock);

	fc_indexName = pstrdup(RelationGetRelationName(fc_indexRel));

	fc_indexInfo = BuildIndexInfo(fc_indexRel);

	/* 这应该在解析时检查 */
	if (!fc_indexInfo->ii_Unique)
		elog(ERROR, "index \"%s\" is not unique", fc_indexName);

	/*
	 * 确定要分配给约束的名称。我们要求约束与基础索引拥有相同的名称；因此，使用索引的现有名称作为默认约束名称，如果用户显式提供其他约束名称，则重命名索引以匹配。
	 */
	fc_constraintName = fc_stmt->idxname;
	if (fc_constraintName == NULL)
		fc_constraintName = fc_indexName;
	else if (strcmp(fc_constraintName, fc_indexName) != 0)
	{
		ereport(NOTICE,
				(errmsg("ALTER TABLE / ADD CONSTRAINT USING INDEX will rename index \"%s\" to \"%s\"",
						fc_indexName, fc_constraintName)));
		RenameRelationInternal(fc_index_oid, fc_constraintName, false, true);
	}

	/* 如果创建主键，则需要额外检查 */
	if (fc_stmt->primary)
		index_check_primary_key(fc_rel, fc_indexInfo, true, fc_stmt);

	/* 请注意，我们当前不支持此处的 EXCLUSION 约束 */
	if (fc_stmt->primary)
		fc_constraintType = CONSTRAINT_PRIMARY;
	else
		fc_constraintType = CONSTRAINT_UNIQUE;

	/* 为约束创建目录条目 */
	fc_flags = INDEX_CONSTR_CREATE_UPDATE_INDEX |
		INDEX_CONSTR_CREATE_REMOVE_OLD_DEPS |
		(fc_stmt->initdeferred ? INDEX_CONSTR_CREATE_INIT_DEFERRED : 0) |
		(fc_stmt->deferrable ? INDEX_CONSTR_CREATE_DEFERRABLE : 0) |
		(fc_stmt->primary ? INDEX_CONSTR_CREATE_MARK_AS_PRIMARY : 0);

	fc_address = index_constraint_create(fc_rel,
									  fc_index_oid,
									  InvalidOid,
									  fc_indexInfo,
									  fc_constraintName,
									  fc_constraintType,
									  fc_flags,
									  allowSystemTableMods,
									  false);	/* is_internal */

	index_close(fc_indexRel, NoLock);

	return fc_address;
}

/*
 * ALTER TABLE 添加约束
 *
 * 返回值是新约束的地址；如果没有添加约束，则返回 InvalidObjectAddress。
 */
static ObjectAddress fc_ATExecAddConstraint(List **fc_wqueue, AlteredTableInfo *fc_tab, Relation fc_rel,
					Constraint *fc_newConstraint, bool fc_recurse, bool fc_is_readd,
					LOCKMODE fc_lockmode)
{
	ObjectAddress fc_address = InvalidObjectAddress;

	Assert(IsA(fc_newConstraint, Constraint));

	/*
	 * 目前，我们预计只会看到 CONSTR_CHECK 和 CONSTR_FOREIGN 节点到达此处（请参阅 parse_utilcmd.c 中的预处理）。
	 * 不过，使用 switch 使将来添加更多代码变得更容易。
	 */
	switch (fc_newConstraint->contype)
	{
		case CONSTR_CHECK:
			fc_address =
				fc_ATAddCheckConstraint(fc_wqueue, fc_tab, fc_rel,
									 fc_newConstraint, fc_recurse, false, fc_is_readd,
									 fc_lockmode);
			break;

		case CONSTR_FOREIGN:

			/*
			 * 分配或验证约束名称
			 */
			if (fc_newConstraint->conname)
			{
				if (ConstraintNameIsUsed(CONSTRAINT_RELATION,
										 RelationGetRelid(fc_rel),
										 fc_newConstraint->conname))
					ereport(ERROR,
							(errcode(ERRCODE_DUPLICATE_OBJECT),
							 errmsg("constraint \"%s\" for relation \"%s\" already exists",
									fc_newConstraint->conname,
									RelationGetRelationName(fc_rel))));
			}
			else
				fc_newConstraint->conname =
					ChooseConstraintName(RelationGetRelationName(fc_rel),
										 fc_ChooseForeignKeyConstraintNameAddition(fc_newConstraint->fk_attrs),
										 "fkey",
										 RelationGetNamespace(fc_rel),
										 NIL);

			fc_address = fc_ATAddForeignKeyConstraint(fc_wqueue, fc_tab, fc_rel,
												fc_newConstraint,
												fc_recurse, false,
												fc_lockmode);
			break;

		default:
			elog(ERROR, "unrecognized constraint type: %d",
				 (int) fc_newConstraint->contype);
	}

	return fc_address;
}

/*
 * 生成新外键约束名称的列名部分，给定引用被引用表的列名列表。
 * 这将与父表名称和“fkey”后缀一起传递给ChooseConstraintName。
 *
 * 我们知道实际使用的字符数会少于NAMEDATALEN，因此一旦生成
 * 了这么多字符，我们可以截断结果。
 *
 * XXX 另见ChooseExtendedStatisticNameAddition和ChooseIndexNameAddition。
 */
static char * fc_ChooseForeignKeyConstraintNameAddition(List *fc_colnames)
{
	char		fc_buf[NAMEDATALEN * 2];
	int			fc_buflen = 0;
	ListCell   *fc_lc;

	fc_buf[0] = '\0';
	foreach(fc_lc, fc_colnames)
	{
		const char *fc_name = strVal(lfirst(fc_lc));

		if (fc_buflen > 0)
			fc_buf[fc_buflen++] = '_';	/* 在名称之间插入 _ */

		/*
		 * 此时我们有 buflen <= NAMEDATALEN。name 应该已经小于
		 * NAMEDATALEN，但为了安全使用 strlcpy。
		 */
		strlcpy(fc_buf + fc_buflen, fc_name, NAMEDATALEN);
		fc_buflen += strlen(fc_buf + fc_buflen);
		if (fc_buflen >= NAMEDATALEN)
			break;
	}
	return pstrdup(fc_buf);
}

/*
 * 为单个表及其子表添加检查约束。如果添加了约束，则返回添加
 * 到父关系的约束的地址，否则返回 InvalidObjectAddress。
 *
 * ATExecAddConstraint 的子例程。
 *
 * 在执行期间，我们必须递归到子表，而不是使用 ALTER TABLE 的正常
 * 准备时间递归。原因是所有的约束 *必须* 使用相同名称，否则
 * 以后将不会被视为相关。如果用户没有明确指定名称，则
 * AddRelationNewConstraints 通常会为子约束分配不同的名称。
 * 为了解决这个问题，我们必须捕获在父表上分配的名称并将其传递下去。
 */
static ObjectAddress fc_ATAddCheckConstraint(List **fc_wqueue, AlteredTableInfo *fc_tab, Relation fc_rel,
					 Constraint *fc_constr, bool fc_recurse, bool fc_recursing,
					 bool fc_is_readd, LOCKMODE fc_lockmode)
{
	List	   *fc_newcons;
	ListCell   *fc_lcon;
	List	   *fc_children;
	ListCell   *fc_child;
	ObjectAddress fc_address = InvalidObjectAddress;

	/* 在顶层，权限检查是在ATPrepCmd中进行的，否则进行检查 */
	if (fc_recursing)
		fc_ATSimplePermissions(AT_AddConstraint, fc_rel, ATT_TABLE | ATT_FOREIGN_TABLE);

	/*
	 * 调用 AddRelationNewConstraints 完成工作，确保它在约束的副本上运行，
	 * 以便 transformExpr 不能修改原始内容。它返回一个已处理约束的列表。
	 *
	 * 如果约束最终与预先存在的约束合并，则它会从返回列表中省略，
	 * 这是我们想要的：我们不需要做任何验证工作。 这只能在子表中发生，
	 * 因为我们不允许在顶层合并。
	 */
	fc_newcons = AddRelationNewConstraints(fc_rel, NIL,
										list_make1(copyObject(fc_constr)),
										fc_recursing | fc_is_readd,	/* 允许合并 */
										!fc_recursing, /* 是本地的 */
										fc_is_readd,	/* is_internal */
										NULL);	/* queryString 在这里不可用 */

	/* 我们不期望这里有多个约束 */
	Assert(list_length(fc_newcons) <= 1);

	/* 将每个待验证的约束添加到第 3 阶段的队列 */
	foreach(fc_lcon, fc_newcons)
	{
		CookedConstraint *fc_ccon = (CookedConstraint *) lfirst(fc_lcon);

		if (!fc_ccon->skip_validation)
		{
			NewConstraint *fc_newcon;

			fc_newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
			fc_newcon->name = fc_ccon->name;
			fc_newcon->contype = fc_ccon->contype;
			fc_newcon->qual = fc_ccon->expr;

			fc_tab->constraints = lappend(fc_tab->constraints, fc_newcon);
		}

		/* 如果是默认命名，保存实际分配的名称 */
		if (fc_constr->conname == NULL)
			fc_constr->conname = fc_ccon->name;

		ObjectAddressSet(fc_address, ConstraintRelationId, fc_ccon->conoid);
	}

	/* 此时我们必须有一个锁定的名称可用 */
	Assert(fc_constr->conname != NULL);

	/* 在同一表被多次访问的情况下提前命令计数器 */
	CommandCounterIncrement();

	/*
	 * 如果约束与现有约束合并，则我们完成了。
	 * 在这种情况下，我们不能递归到子表，因为它们
	 * 已经有了约束，再次访问会导致 coninhcount 的
	 * 值不正确。
	 */
	if (fc_newcons == NIL)
		return fc_address;

	/*
	 * 如果添加 NO INHERIT 约束，则不需要查找我们的子表。
	 */
	if (fc_constr->is_no_inherit)
		return fc_address;

	/*
	 * 根据需要向子级传播。与大多数其他 ALTER
	 * 例程不同，我们必须一次进行一层递归；我们不能
	 * 使用 find_all_inheritors 一次性完成。
	 */
	fc_children =
		find_inheritance_children(RelationGetRelid(fc_rel), fc_lockmode);

	/*
	 * 检查是否仅在 ALTER TABLE 时指定了 ONLY。如果是，则仅在当前没有子项时允许创建约束。否则返回错误。
	 */
	if (!fc_recurse && fc_children != NIL)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
				 errmsg("constraint must be added to child tables too")));

	foreach(fc_child, fc_children)
	{
		Oid			fc_childrelid = lfirst_oid(fc_child);
		Relation	fc_childrel;
		AlteredTableInfo *fc_childtab;

		/* find_inheritance_children 已经获得锁定 */
		fc_childrel = table_open(fc_childrelid, NoLock);
		fc_CheckAlterTableIsSafe(fc_childrel);

		/* 查找或创建该表的工作队列条目 */
		fc_childtab = fc_ATGetQueueEntry(fc_wqueue, fc_childrel);

		/* 递归到子项 */
		fc_ATAddCheckConstraint(fc_wqueue, fc_childtab, fc_childrel,
							 fc_constr, fc_recurse, true, fc_is_readd, fc_lockmode);

		table_close(fc_childrel, NoLock);
	}

	return fc_address;
}

/*
 * 为单个表添加外键约束；返回新约束的地址。
 *
 * ATExecAddConstraint 的子程序。必须已经对 rel 持有独占锁，并对其进行了适当的有效性检查。但是，我们在这里进行权限检查。
 *
 * 当被引用或引用的表（或两者）被分区时，需要多个 pg_constraint 行——每个分区表和每个侧面的每个分区一个（幸运的是，不是每个组合一个）。我们还需要在被引用侧的每个叶子分区上有动作触发器，以及在引用侧的每个叶子分区上有检查触发器。
 */
static ObjectAddress fc_ATAddForeignKeyConstraint(List **fc_wqueue, AlteredTableInfo *fc_tab, Relation fc_rel,
						  Constraint *fc_fkconstraint,
						  bool fc_recurse, bool fc_recursing, LOCKMODE fc_lockmode)
{
	Relation	fc_pkrel;
	int16		fc_pkattnum[INDEX_MAX_KEYS];
	int16		fc_fkattnum[INDEX_MAX_KEYS];
	Oid			fc_pktypoid[INDEX_MAX_KEYS];
	Oid			fc_fktypoid[INDEX_MAX_KEYS];
	Oid			fc_opclasses[INDEX_MAX_KEYS];
	Oid			fc_pfeqoperators[INDEX_MAX_KEYS];
	Oid			fc_ppeqoperators[INDEX_MAX_KEYS];
	Oid			fc_ffeqoperators[INDEX_MAX_KEYS];
	int16		fc_fkdelsetcols[INDEX_MAX_KEYS];
	int			fc_i;
	int			fc_numfks,
				fc_numpks,
				fc_numfkdelsetcols;
	Oid			fc_indexOid;
	bool		fc_old_check_ok;
	ObjectAddress fc_address;
	ListCell   *fc_old_pfeqop_item = list_head(fc_fkconstraint->old_conpfeqop);

	/*
	 * 在 pk 表上获取 ShareRowExclusiveLock，以防有人在我们下面删除行。
	 */
	if (OidIsValid(fc_fkconstraint->old_pktable_oid))
		fc_pkrel = table_open(fc_fkconstraint->old_pktable_oid, ShareRowExclusiveLock);
	else
		fc_pkrel = table_openrv(fc_fkconstraint->pktable, ShareRowExclusiveLock);

	/*
	 * 有效性检查（权限检查等到我们有列编号后进行）
	 */
	if (fc_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
	{
		if (!fc_recurse)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("cannot use ONLY for foreign key on partitioned table \"%s\" referencing relation \"%s\"",
							RelationGetRelationName(fc_rel),
							RelationGetRelationName(fc_pkrel))));
		if (fc_fkconstraint->skip_validation && !fc_fkconstraint->initially_valid)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("cannot add NOT VALID foreign key on partitioned table \"%s\" referencing relation \"%s\"",
							RelationGetRelationName(fc_rel),
							RelationGetRelationName(fc_pkrel)),
					 errdetail("This feature is not yet supported on partitioned tables.")));
	}

	if (fc_pkrel->rd_rel->relkind != RELKIND_RELATION &&
		fc_pkrel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("referenced relation \"%s\" is not a table",
						RelationGetRelationName(fc_pkrel))));

	if (!allowSystemTableMods && IsSystemRelation(fc_pkrel))
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("permission denied: \"%s\" is a system catalog",
						RelationGetRelationName(fc_pkrel))));

	/*
	 * 从永久或未记录表到临时表的引用，以及从永久表到未记录表的引用是不允许的，因为被引用的数据可能会在我们下面消失。 从临时表到任何其他表类型的引用也不被允许，因为其他后端可能需要在永久表上运行 RI 触发器，但它们不能可靠地看到其他后端的本地缓冲区中的元组。
	 */
	switch (fc_rel->rd_rel->relpersistence)
	{
		case RELPERSISTENCE_PERMANENT:
			if (!RelationIsPermanent(fc_pkrel))
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
						 errmsg("constraints on permanent tables may reference only permanent tables")));
			break;
		case RELPERSISTENCE_UNLOGGED:
			if (!RelationIsPermanent(fc_pkrel)
				&& fc_pkrel->rd_rel->relpersistence != RELPERSISTENCE_UNLOGGED)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
						 errmsg("constraints on unlogged tables may reference only permanent or unlogged tables")));
			break;
		case RELPERSISTENCE_TEMP:
			if (fc_pkrel->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
						 errmsg("constraints on temporary tables may reference only temporary tables")));
			if (!fc_pkrel->rd_islocaltemp || !fc_rel->rd_islocaltemp)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
			break;
	}

	/*
	 * 查找引用属性以确保它们存在，并记录它们的 attnums 和类型 OID。
	 */
	MemSet(fc_pkattnum, 0, sizeof(fc_pkattnum));
	MemSet(fc_fkattnum, 0, sizeof(fc_fkattnum));
	MemSet(fc_pktypoid, 0, sizeof(fc_pktypoid));
	MemSet(fc_fktypoid, 0, sizeof(fc_fktypoid));
	MemSet(fc_opclasses, 0, sizeof(fc_opclasses));
	MemSet(fc_pfeqoperators, 0, sizeof(fc_pfeqoperators));
	MemSet(fc_ppeqoperators, 0, sizeof(fc_ppeqoperators));
	MemSet(fc_ffeqoperators, 0, sizeof(fc_ffeqoperators));
	MemSet(fc_fkdelsetcols, 0, sizeof(fc_fkdelsetcols));

	fc_numfks = fc_transformColumnNameList(RelationGetRelid(fc_rel),
									 fc_fkconstraint->fk_attrs,
									 fc_fkattnum, fc_fktypoid);

	fc_numfkdelsetcols = fc_transformColumnNameList(RelationGetRelid(fc_rel),
											  fc_fkconstraint->fk_del_set_cols,
											  fc_fkdelsetcols, NULL);
	fc_validateFkOnDeleteSetColumns(fc_numfks, fc_fkattnum,
								 fc_numfkdelsetcols, fc_fkdelsetcols,
								 fc_fkconstraint->fk_del_set_cols);

	/*
	 * 如果省略了被引用表的属性列表，则查找主键的定义并使用它。否则，验证提供的属性列表。在任何情况下，发现索引 OID 和索引 opclasses，以及属性的 attnums 和类型 OID。
	 */
	if (fc_fkconstraint->pk_attrs == NIL)
	{
		fc_numpks = fc_transformFkeyGetPrimaryKey(fc_pkrel, &fc_indexOid,
											&fc_fkconstraint->pk_attrs,
											fc_pkattnum, fc_pktypoid,
											fc_opclasses);
	}
	else
	{
		fc_numpks = fc_transformColumnNameList(RelationGetRelid(fc_pkrel),
										 fc_fkconstraint->pk_attrs,
										 fc_pkattnum, fc_pktypoid);
		/* 查找与列列表匹配的索引 */
		fc_indexOid = fc_transformFkeyCheckAttrs(fc_pkrel, fc_numpks, fc_pkattnum,
										   fc_opclasses);
	}

	/*
	 * 现在我们可以检查权限。
	 */
	fc_checkFkeyPermissions(fc_pkrel, fc_pkattnum, fc_numpks);

	/*
	 * 检查一些生成列的事项。
	 */
	for (fc_i = 0; fc_i < fc_numfks; fc_i++)
	{
		char		fc_attgenerated = TupleDescAttr(RelationGetDescr(fc_rel), fc_fkattnum[fc_i] - 1)->attgenerated;

		if (fc_attgenerated)
		{
			/*
			 * 根据 SQL 标准检查 UPDATE/DELETE 操作的限制
			 */
			if (fc_fkconstraint->fk_upd_action == FKCONSTR_ACTION_SETNULL ||
				fc_fkconstraint->fk_upd_action == FKCONSTR_ACTION_SETDEFAULT ||
				fc_fkconstraint->fk_upd_action == FKCONSTR_ACTION_CASCADE)
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("invalid %s action for foreign key constraint containing generated column",
								"ON UPDATE")));
			if (fc_fkconstraint->fk_del_action == FKCONSTR_ACTION_SETNULL ||
				fc_fkconstraint->fk_del_action == FKCONSTR_ACTION_SETDEFAULT)
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("invalid %s action for foreign key constraint containing generated column",
								"ON DELETE")));
		}
	}

	/*
	 * 查找要在约束中使用的相等运算符。
	 *
	 * 注意，我们必须小心实际 PK 列类型与运算符类声明的输入类型之间的差异，后者可能仅与之二进制兼容。声明的 opcintype 是探测 pg_amop 的正确参数。
	 */
	if (fc_numfks != fc_numpks)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_FOREIGN_KEY),
				 errmsg("number of referencing and referenced columns for foreign key disagree")));

	/*
	 * 根据先前的约束，我们可能可以避免扫描表来验证这个约束。见下文。
	 */
	fc_old_check_ok = (fc_fkconstraint->old_conpfeqop != NIL);
	Assert(!fc_old_check_ok || fc_numfks == list_length(fc_fkconstraint->old_conpfeqop));

	for (fc_i = 0; fc_i < fc_numpks; fc_i++)
	{
		Oid			fc_pktype = fc_pktypoid[fc_i];
		Oid			fc_fktype = fc_fktypoid[fc_i];
		Oid			fc_fktyped;
		HeapTuple	fc_cla_ht;
		Form_pg_opclass fc_cla_tup;
		Oid			fc_amid;
		Oid			fc_opfamily;
		Oid			fc_opcintype;
		Oid			fc_pfeqop;
		Oid			fc_ppeqop;
		Oid			fc_ffeqop;
		int16		fc_eqstrategy;
		Oid			fc_pfeqop_right;

		/* 我们需要从 pg_opclass 条目中获取几个字段 */
		fc_cla_ht = SearchSysCache1(CLAOID, ObjectIdGetDatum(fc_opclasses[fc_i]));
		if (!HeapTupleIsValid(fc_cla_ht))
			elog(ERROR, "cache lookup failed for opclass %u", fc_opclasses[fc_i]);
		fc_cla_tup = (Form_pg_opclass) GETSTRUCT(fc_cla_ht);
		fc_amid = fc_cla_tup->opcmethod;
		fc_opfamily = fc_cla_tup->opcfamily;
		fc_opcintype = fc_cla_tup->opcintype;
		ReleaseSysCache(fc_cla_ht);

		/*
		 * 检查它是否是一个btree；目前这个检查永远不会失败，因为没有其他
		 * 索引 AM 支持唯一索引。如果我们真的有其他类型的
		 * 唯一索引，我们需要一种方法来确定哪个操作符
		 * 策略编号是等于。 （坚持每个这样的索引 AM 使用 btree 的编号来表示等于，是否合理？）
		 */
		if (fc_amid != BTREE_AM_OID)
			elog(ERROR, "only b-tree indexes are supported for foreign keys");
		fc_eqstrategy = BTEqualStrategyNumber;

		/*
		 * 对于索引来说，最好有一个主等于操作符。
		 * 我们将用于 PK = PK 比较。
		 */
		fc_ppeqop = get_opfamily_member(fc_opfamily, fc_opcintype, fc_opcintype,
									 fc_eqstrategy);

		if (!OidIsValid(fc_ppeqop))
			elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
				 fc_eqstrategy, fc_opcintype, fc_opcintype, fc_opfamily);

		/*
		 * 是否有精确匹配 FK 类型的等于操作符？假设
		 * 我们应在此查找任何域。
		 */
		fc_fktyped = getBaseType(fc_fktype);

		fc_pfeqop = get_opfamily_member(fc_opfamily, fc_opcintype, fc_fktyped,
									 fc_eqstrategy);
		if (OidIsValid(fc_pfeqop))
		{
			fc_pfeqop_right = fc_fktyped;
			fc_ffeqop = get_opfamily_member(fc_opfamily, fc_fktyped, fc_fktyped,
										 fc_eqstrategy);
		}
		else
		{
			/* 保持编译器安静 */
			fc_pfeqop_right = InvalidOid;
			fc_ffeqop = InvalidOid;
		}

		if (!(OidIsValid(fc_pfeqop) && OidIsValid(fc_ffeqop)))
		{
			/*
			 * 否则，查找从 FK 类型到
			 * opcintype 的隐式转换，如果找到，使用主等于操作符。
			 * 这有点棘手，因为 opcintype 可能是一个多态
			 * 类型，例如 ANYARRAY 或 ANYENUM；所以我们必须测试的是
			 * 两个实际列类型是否可以同时转换为
			 * 该类型。 （否则，我们将无法拒绝组合，例如
			 * int[] 和 point[]。）
			 */
			Oid			fc_input_typeids[2];
			Oid			fc_target_typeids[2];

			fc_input_typeids[0] = fc_pktype;
			fc_input_typeids[1] = fc_fktype;
			fc_target_typeids[0] = fc_opcintype;
			fc_target_typeids[1] = fc_opcintype;
			if (can_coerce_type(2, fc_input_typeids, fc_target_typeids,
								COERCION_IMPLICIT))
			{
				fc_pfeqop = fc_ffeqop = fc_ppeqop;
				fc_pfeqop_right = fc_opcintype;
			}
		}

		if (!(OidIsValid(fc_pfeqop) && OidIsValid(fc_ffeqop)))
			ereport(ERROR,
					(errcode(ERRCODE_DATATYPE_MISMATCH),
					 errmsg("foreign key constraint \"%s\" cannot be implemented",
							fc_fkconstraint->conname),
					 errdetail("Key columns \"%s\" and \"%s\" "
							   "are of incompatible types: %s and %s.",
							   strVal(list_nth(fc_fkconstraint->fk_attrs, fc_i)),
							   strVal(list_nth(fc_fkconstraint->pk_attrs, fc_i)),
							   format_type_be(fc_fktype),
							   format_type_be(fc_pktype))));

		if (fc_old_check_ok)
		{
			/*
			 * 当 pfeqop 发生变化时，重新验证约束。我们可以
			 * 允许同一操作族的变化，但这会增加微妙的复杂性
			 * 而对核心类型没有任何具体的好处。我们无需
			 * 评估 ppeqop 或 ffeqop，因为 RI_Initial_Check() 并不使用它们。
			 */
			fc_old_check_ok = (fc_pfeqop == lfirst_oid(fc_old_pfeqop_item));
			fc_old_pfeqop_item = lnext(fc_fkconstraint->old_conpfeqop,
									fc_old_pfeqop_item);
		}
		if (fc_old_check_ok)
		{
			Oid			fc_old_fktype;
			Oid			fc_new_fktype;
			CoercionPathType fc_old_pathtype;
			CoercionPathType fc_new_pathtype;
			Oid			fc_old_castfunc;
			Oid			fc_new_castfunc;
			Form_pg_attribute fc_attr = TupleDescAttr(fc_tab->oldDesc,
												   fc_fkattnum[fc_i] - 1);

			/*
			 * 确定从每种旧的和新的 FK 侧
			 * 列类型到 pfeqop 的正确（外部）操作数类型的转换路径。
			 * 我们可以假设 pg_constraint.conkey 没有变化。
			 */
			fc_old_fktype = fc_attr->atttypid;
			fc_new_fktype = fc_fktype;
			fc_old_pathtype = fc_findFkeyCast(fc_pfeqop_right, fc_old_fktype,
										&fc_old_castfunc);
			fc_new_pathtype = fc_findFkeyCast(fc_pfeqop_right, fc_new_fktype,
										&fc_new_castfunc);

			/*
			 * 在将FK列的转换更改为其pfeqop操作数时，重新验证约束。对于该评估，二进制强制转换相当于根本不进行转换。虽然类型实现者应该在设计隐式转换时考虑操作的一致性，例如相等性，但我们不能假设他们已经这样做了。
			 *
			 * 一个具有多态参数的函数可能会根据get_fn_expr_argtype()任意改变其行为。因此，当转换目标是多态时，只有在输入类型完全没有变化的情况下，我们才会避免重新验证。仅考虑核心数据类型和操作符类，这个要求不会阻止任何潜在的优化。
			 *
			 * 如果转换是从基本类型转换到其上的域，那么该域类型必须是唯一索引的opcintype。因此，主键列必然是域类型。由于约束之前是有效的，外侧的所有值必然存在于主侧，并反过来符合域。因此，我们不需要在这里特别对待域。
			 *
			 * 由于我们要求所有排序都有相同的相等性概念（它们确实如此，因为texteq简化为按位相等），我们在这里不进行排序比较。
			 *
			 * 我们不必直接考虑PK类型。它必然可以被二进制强制转换为唯一索引列的opcintype，ri_triggers.c只处理以该opcintype为依据的PK数据。改变opcintype也会改变pfeqop。
			 */
			fc_old_check_ok = (fc_new_pathtype == fc_old_pathtype &&
							fc_new_castfunc == fc_old_castfunc &&
							(!IsPolymorphicType(fc_pfeqop_right) ||
							 fc_new_fktype == fc_old_fktype));
		}

		fc_pfeqoperators[fc_i] = fc_pfeqop;
		fc_ppeqoperators[fc_i] = fc_ppeqop;
		fc_ffeqoperators[fc_i] = fc_ffeqop;
	}

	/* 首先，创建约束目录条目本身。 */
	fc_address = fc_addFkConstraint(addFkBothSides,
							  fc_fkconstraint->conname, fc_fkconstraint, fc_rel, fc_pkrel,
							  fc_indexOid,
							  InvalidOid,	/* 没有父约束 */
							  fc_numfks,
							  fc_pkattnum,
							  fc_fkattnum,
							  fc_pfeqoperators,
							  fc_ppeqoperators,
							  fc_ffeqoperators,
							  fc_numfkdelsetcols,
							  fc_fkdelsetcols,
							  false);

	/* 接下来处理被引用方的动作触发器并递归 */
	fc_addFkRecurseReferenced(fc_fkconstraint, fc_rel, fc_pkrel,
						   fc_indexOid,
						   fc_address.objectId,
						   fc_numfks,
						   fc_pkattnum,
						   fc_fkattnum,
						   fc_pfeqoperators,
						   fc_ppeqoperators,
						   fc_ffeqoperators,
						   fc_numfkdelsetcols,
						   fc_fkdelsetcols,
						   fc_old_check_ok,
						   InvalidOid, InvalidOid);

	/* 最后创建引用方的检查触发器并递归 */
	fc_addFkRecurseReferencing(fc_wqueue, fc_fkconstraint, fc_rel, fc_pkrel,
							fc_indexOid,
							fc_address.objectId,
							fc_numfks,
							fc_pkattnum,
							fc_fkattnum,
							fc_pfeqoperators,
							fc_ppeqoperators,
							fc_ffeqoperators,
							fc_numfkdelsetcols,
							fc_fkdelsetcols,
							fc_old_check_ok,
							fc_lockmode,
							InvalidOid, InvalidOid);

	/*
	 * 完成。关闭pk表，但在提交之前保持锁定。
	 */
	table_close(fc_pkrel, NoLock);

	return fc_address;
}

/*
 * validateFkOnDeleteSetColumns
 *		验证在ON DELETE SET NULL/DEFAULT (...) 列表中使用的列是否有效。
 */
void fc_validateFkOnDeleteSetColumns(int fc_numfks, const int16 *fc_fkattnums,
							 int fc_numfksetcols, const int16 *fc_fksetcolsattnums,
							 List *fc_fksetcols)
{
	for (int fc_i = 0; fc_i < fc_numfksetcols; fc_i++)
	{
		int16		fc_setcol_attnum = fc_fksetcolsattnums[fc_i];
		bool		fc_seen = false;

		for (int fc_j = 0; fc_j < fc_numfks; fc_j++)
		{
			if (fc_fkattnums[fc_j] == fc_setcol_attnum)
			{
				fc_seen = true;
				break;
			}
		}

		if (!fc_seen)
		{
			char	   *fc_col = strVal(list_nth(fc_fksetcols, fc_i));

			ereport(ERROR,
					(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
					 errmsg("column \"%s\" referenced in ON DELETE SET action must be part of foreign key", fc_col)));
		}
	}
}

/*
 * addFkConstraint
 *		安装pg_constraint条目以实现外键约束。
 *		调用者必须根据需要单独调用addFkRecurseReferenced和
 *		addFkRecurseReferencing，以安装pg_trigger条目
 *		并（对于分区表）递归到分区。
 *
 * fkside: 要创建的外键的一方（或者两者）。调用者应
 *      如果是addFkReferencedSide则调用addFkRecurseReferenced，
 *      如果是addFkReferencingSide则调用addFkRecurseReferencing，或者如果是
 *      addFkBothSides则两者都调用。
 * constraintname: 要添加的约束的基本名称，
 *      如果fkconstraint->conname未设置则复制到该位置
 * fkconstraint: 要添加的约束
 * rel: 根引用关系
 * pkrel: 被引用的关系；如果递归，可能是一个分区
 * indexOid: 实现此约束的索引（在pkrel上）的OID
 * parentConstr: 父约束的OID；如果这是一个
 *      顶级约束，则为InvalidOid
 * numfks: 外键中的列数
 * pkattnum: 被引用属性的attnum数组
 * fkattnum: 引用属性的attnum数组
 * pf/pp/ffeqoperators: 列之间的操作符的OID数组
 * numfkdelsetcols: ON DELETE SET NULL/DEFAULT中的列数
 *      (...)子句
 * fkdelsetcols: ON DELETE SET
 *      NULL/DEFAULT子句中列的attnum数组
 */
static ObjectAddress fc_addFkConstraint(addFkConstraintSides fc_fkside,
				char *fc_constraintname, Constraint *fc_fkconstraint,
				Relation fc_rel, Relation fc_pkrel, Oid fc_indexOid, Oid fc_parentConstr,
				int fc_numfks, int16 *fc_pkattnum,
				int16 *fc_fkattnum, Oid *fc_pfeqoperators, Oid *fc_ppeqoperators,
				Oid *fc_ffeqoperators, int fc_numfkdelsetcols, int16 *fc_fkdelsetcols,
				bool fc_is_internal)
{
	ObjectAddress fc_address;
	Oid			fc_constrOid;
	char	   *fc_conname;
	bool		fc_conislocal;
	int			fc_coninhcount;
	bool		fc_connoinherit;

	/*
	 * 验证每个被引用分区的relkind。在顶层，这
	 * 与先前的检查冗余，但在递归时我们需要它。
	 */
	if (fc_pkrel->rd_rel->relkind != RELKIND_RELATION &&
		fc_pkrel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("referenced relation \"%s\" is not a table",
						RelationGetRelationName(fc_pkrel))));

	/*
	 * 调用者提供约束名称；然而，它可能在
	 * 此分区中使用，因此在这种情况下想出一个不同的名称。
	 */
	if (ConstraintNameIsUsed(CONSTRAINT_RELATION,
							 RelationGetRelid(fc_rel),
							 fc_constraintname))
		fc_conname = ChooseConstraintName(RelationGetRelationName(fc_rel),
									   fc_ChooseForeignKeyConstraintNameAddition(fc_fkconstraint->fk_attrs),
									   "fkey",
									   RelationGetNamespace(fc_rel), NIL);
	else
		fc_conname = fc_constraintname;

	if (fc_fkconstraint->conname == NULL)
		fc_fkconstraint->conname = pstrdup(fc_conname);

	if (OidIsValid(fc_parentConstr))
	{
		fc_conislocal = false;
		fc_coninhcount = 1;
		fc_connoinherit = false;
	}
	else
	{
		fc_conislocal = true;
		fc_coninhcount = 0;

		/*
		 * 始终为分区表继承，永远不要为遗留继承
		 */
		fc_connoinherit = fc_rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE;
	}

	/*
	 * 在pg_constraint中记录FK约束。
	 */
	fc_constrOid = CreateConstraintEntry(fc_conname,
									  RelationGetNamespace(fc_rel),
									  CONSTRAINT_FOREIGN,
									  fc_fkconstraint->deferrable,
									  fc_fkconstraint->initdeferred,
									  fc_fkconstraint->initially_valid,
									  fc_parentConstr,
									  RelationGetRelid(fc_rel),
									  fc_fkattnum,
									  fc_numfks,
									  fc_numfks,
									  InvalidOid,	/* 不是域约束 */
									  fc_indexOid,
									  RelationGetRelid(fc_pkrel),
									  fc_pkattnum,
									  fc_pfeqoperators,
									  fc_ppeqoperators,
									  fc_ffeqoperators,
									  fc_numfks,
									  fc_fkconstraint->fk_upd_action,
									  fc_fkconstraint->fk_del_action,
									  fc_fkdelsetcols,
									  fc_numfkdelsetcols,
									  fc_fkconstraint->fk_matchtype,
									  NULL, /* 没有排除约束 */
									  NULL, /* 没有检查约束 */
									  NULL,
									  fc_conislocal,	/* islocal */
									  fc_coninhcount,	/* inhcount */
									  fc_connoinherit, /* conNoInherit */
									  fc_is_internal); /* is_internal */

	ObjectAddressSet(fc_address, ConstraintRelationId, fc_constrOid);

	/*
	 * 在分区情况下，为这个约束创建依赖条目。 （对于非分区情况，相关条目由CreateConstraintEntry创建。）
	 *
	 * 在被引用的一方，我们需要约束对其父约束有内部依赖关系；这意味着该约束不能单独删除 -- 只能通过父约束删除。这也意味着包含的分区不能单独删除，但可以被分离，此时将移除此依赖关系（在确认没有行通过此外键被引用之后）。
	 *
	 * 处理引用方时，我们通过特殊的分区依赖关系链接约束：父约束是主要依赖，外键所在的分区是次要依赖。这样，如果这两个对象中的任意一个被删除，该约束就会被删除。
	 *
	 * 请注意，这仅在分区中的子 pg_constraint 行上是必要的；最上层行不需要任何这一切。
	 */
	if (OidIsValid(fc_parentConstr))
	{
		ObjectAddress fc_referenced;

		ObjectAddressSet(fc_referenced, ConstraintRelationId, fc_parentConstr);

		Assert(fc_fkside != addFkBothSides);
		if (fc_fkside == addFkReferencedSide)
			recordDependencyOn(&fc_address, &fc_referenced, DEPENDENCY_INTERNAL);
		else
		{
			recordDependencyOn(&fc_address, &fc_referenced, DEPENDENCY_PARTITION_PRI);
			ObjectAddressSet(fc_referenced, RelationRelationId, RelationGetRelid(fc_rel));
			recordDependencyOn(&fc_address, &fc_referenced, DEPENDENCY_PARTITION_SEC);
		}
	}

	/* 使新约束可见，以防我们添加更多 */
	CommandCounterIncrement();

	return fc_address;
}

/*
 * addFkRecurseReferenced
 *		用于外键创建被引用方的递归助手，
 *		它创建动作触发器并递归
 *
 * 如果被引用的关系是一个普通关系，则创建实现约束所需的动作触发器。如果被引用的关系是一个分区表，则我们为其创建一个引用引用方的父级的 pg_constraint 行，并对每个分区递归调用此例程。
 *
 * fkconstraint: 正在添加的约束
 * rel: 根引用关系
 * pkrel: 被引用关系；可能是分区，如果递归
 * indexOid: 实现此约束的索引的 OID（在 pkrel 上）
 * parentConstr: 父约束的 OID；如果这是一个
 *      顶层约束则为 InvalidOid
 * numfks: 外键中的列数
 * pkattnum: 被引用属性的 attnum 数组
 * fkattnum: 引用属性的 attnum 数组
 * numfkdelsetcols: 在 ON DELETE SET
 *      NULL/DEFAULT (...) 子句中的列数
 * fkdelsetcols: 在 ON DELETE SET
 *      NULL/DEFAULT 子句中的列的 attnum 数组
 * pf/pp/ffeqoperators: 列之间的操作符的 OID 数组
 * old_check_ok: 如果此约束替换了已验证的现有约束，则为 true
 *      （因此不需要验证此约束）
 * parentDelTrigger 和 parentUpdTrigger: 当在分区上递归调用时，
 *      DELETE 和 UPDATE 的父动作触发器的 OID。
 */
static void fc_addFkRecurseReferenced(Constraint *fc_fkconstraint, Relation fc_rel,
					   Relation fc_pkrel, Oid fc_indexOid, Oid fc_parentConstr,
					   int fc_numfks,
					   int16 *fc_pkattnum, int16 *fc_fkattnum, Oid *fc_pfeqoperators,
					   Oid *fc_ppeqoperators, Oid *fc_ffeqoperators,
					   int fc_numfkdelsetcols, int16 *fc_fkdelsetcols,
					   bool fc_old_check_ok,
					   Oid fc_parentDelTrigger, Oid fc_parentUpdTrigger)
{
	Oid			fc_deleteTriggerOid,
				fc_updateTriggerOid;

	Assert(CheckRelationLockedByMe(fc_pkrel, ShareRowExclusiveLock, true));
	Assert(CheckRelationLockedByMe(fc_rel, ShareRowExclusiveLock, true));

	/*
	 * 创建执行约束的动作触发器。
	 */
	fc_createForeignKeyActionTriggers(fc_rel, RelationGetRelid(fc_pkrel),
								   fc_fkconstraint,
								   fc_parentConstr, fc_indexOid,
								   fc_parentDelTrigger, fc_parentUpdTrigger,
								   &fc_deleteTriggerOid, &fc_updateTriggerOid);

	/*
	 * 如果被引用的表是分区的，则递归调用我们自己以处理
	 * 每个分区。我们需要为每个
	 * 分区创建一个 pg_constraint 行，此外还有父表的 pg_constraint 行。
	 */
	if (fc_pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
	{
		PartitionDesc fc_pd = RelationGetPartitionDesc(fc_pkrel, true);

		for (int fc_i = 0; fc_i < fc_pd->nparts; fc_i++)
		{
			Relation	fc_partRel;
			AttrMap    *fc_map;
			AttrNumber *fc_mapped_pkattnum;
			Oid			fc_partIndexId;
			ObjectAddress fc_address;

			/* XXX 是否在之前获取这些锁会更好？ */
			fc_partRel = table_open(fc_pd->oids[fc_i], ShareRowExclusiveLock);

			/*
			 * 将外键定义中引用方的属性编号映射到分区的列布局。
			 */
			fc_map = build_attrmap_by_name_if_req(RelationGetDescr(fc_partRel),
											   RelationGetDescr(fc_pkrel));
			if (fc_map)
			{
				fc_mapped_pkattnum = palloc(sizeof(AttrNumber) * fc_numfks);
				for (int fc_j = 0; fc_j < fc_numfks; fc_j++)
					fc_mapped_pkattnum[fc_j] = fc_map->attnums[fc_pkattnum[fc_j] - 1];
			}
			else
				fc_mapped_pkattnum = fc_pkattnum;

			/* 确定在此层使用的索引 */
			fc_partIndexId = index_get_partition(fc_partRel, fc_indexOid);
			if (!OidIsValid(fc_partIndexId))
				elog(ERROR, "index for %u not found in partition %s",
					 fc_indexOid, RelationGetRelationName(fc_partRel));

			/* 在此层创建条目 ... */
			fc_address = fc_addFkConstraint(addFkReferencedSide,
									  fc_fkconstraint->conname, fc_fkconstraint, fc_rel,
									  fc_partRel, fc_partIndexId, fc_parentConstr,
									  fc_numfks, fc_mapped_pkattnum,
									  fc_fkattnum, fc_pfeqoperators, fc_ppeqoperators,
									  fc_ffeqoperators, fc_numfkdelsetcols,
									  fc_fkdelsetcols, true);
			/* ... 并递归到我们的子项 */
			fc_addFkRecurseReferenced(fc_fkconstraint, fc_rel, fc_partRel,
								   fc_partIndexId, fc_address.objectId, fc_numfks,
								   fc_mapped_pkattnum, fc_fkattnum,
								   fc_pfeqoperators, fc_ppeqoperators, fc_ffeqoperators,
								   fc_numfkdelsetcols, fc_fkdelsetcols,
								   fc_old_check_ok,
								   fc_deleteTriggerOid, fc_updateTriggerOid);

			/* 完成 - 清理（但保留锁） */
			table_close(fc_partRel, NoLock);
			if (fc_map)
			{
				pfree(fc_mapped_pkattnum);
				free_attrmap(fc_map);
			}
		}
	}
}

/*
 * addFkRecurseReferencing
 *		用于外键创建的引用方的递归助手，
 *		创建检查触发器并递归
 *
 * 如果引用关系是普通关系，创建实现约束所需的检查
 * 触发器，并为第3阶段约束验证做好准备。如果引用关系是分区表,
 * 则为其创建一个 pg_constraint 行，并对每个
 * 分区递归调用此例程。
 *
 * 我们假设被引用关系已锁定，以防止并发
 * 删除。如果是分区关系，则每个分区必须如此
 * 锁定。
 *
 * wqueue: ALTER TABLE 工作队列；在不作为 ALTER TABLE 序列的一部分时为 NULL。
 * fkconstraint: 正在添加的约束
 * rel: 引用关系；如果递归，则可能是一个分区
 * pkrel: 根被引用关系
 * indexOid: 实现此约束的索引（在 pkrel 上）的 OID
 * parentConstr: 父约束的 OID（总是存在一个）
 * numfks: 外键中的列数
 * pkattnum: 被引用属性的 attnum 数组
 * fkattnum: 引用属性的 attnum 数组
 * pf/pp/ffeqoperators: 列之间操作符的 OID 数组
 * numfkdelsetcols: ON DELETE SET NULL/DEFAULT (...) 子句中的列数
 * fkdelsetcols: ON DELETE SET NULL/DEFAULT 子句中的列的 attnum 数组
 * old_check_ok: 如果此约束替换了一个已存在的且
 *      已经验证的约束，则为 true（因此此约束不需要验证）
 * lockmode: 在递归时获取分区的锁模式
 * parentInsTrigger 和 parentUpdTrigger: 在递归调用
 *      分区时，分别用于 INSERT 和 UPDATE 的父检查触发器的 OID。
 */
static void fc_addFkRecurseReferencing(List **fc_wqueue, Constraint *fc_fkconstraint, Relation fc_rel,
						Relation fc_pkrel, Oid fc_indexOid, Oid fc_parentConstr,
						int fc_numfks, int16 *fc_pkattnum, int16 *fc_fkattnum,
						Oid *fc_pfeqoperators, Oid *fc_ppeqoperators, Oid *fc_ffeqoperators,
						int fc_numfkdelsetcols, int16 *fc_fkdelsetcols,
						bool fc_old_check_ok, LOCKMODE fc_lockmode,
						Oid fc_parentInsTrigger, Oid fc_parentUpdTrigger)
{
	Oid			fc_insertTriggerOid,
				fc_updateTriggerOid;

	Assert(OidIsValid(fc_parentConstr));
	Assert(CheckRelationLockedByMe(fc_rel, ShareRowExclusiveLock, true));
	Assert(CheckRelationLockedByMe(fc_pkrel, ShareRowExclusiveLock, true));

	if (fc_rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("foreign key constraints are not supported on foreign tables")));

	/*
	 * 向其添加检查触发器，并在必要时安排在第3阶段进行
	 * 检查。
	 *
	 * 如果关系是分区的，则深入到其分区进行处理。
	 */
	fc_createForeignKeyCheckTriggers(RelationGetRelid(fc_rel),
								  RelationGetRelid(fc_pkrel),
								  fc_fkconstraint,
								  fc_parentConstr,
								  fc_indexOid,
								  fc_parentInsTrigger, fc_parentUpdTrigger,
								  &fc_insertTriggerOid, &fc_updateTriggerOid);

	if (fc_rel->rd_rel->relkind == RELKIND_RELATION)
	{
		/*
		 * 告知第3阶段检查约束是否已被现有
		 * 行满足。在创建表期间，我们可以跳过此检查，
		 * 当在 ADD FOREIGN KEY 命令中显式指定 NOT VALID 时，
		 * 以及在我们在 SET DATA TYPE 操作后重建约束时，
		 * 此操作不会影响其有效性。
		 */
		if (fc_wqueue && !fc_old_check_ok && !fc_fkconstraint->skip_validation)
		{
			NewConstraint *fc_newcon;
			AlteredTableInfo *fc_tab;

			fc_tab = fc_ATGetQueueEntry(fc_wqueue, fc_rel);

			fc_newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
			fc_newcon->name = get_constraint_name(fc_parentConstr);
			fc_newcon->contype = CONSTR_FOREIGN;
			fc_newcon->refrelid = RelationGetRelid(fc_pkrel);
			fc_newcon->refindid = fc_indexOid;
			fc_newcon->conid = fc_parentConstr;
			fc_newcon->qual = (Node *) fc_fkconstraint;

			fc_tab->constraints = lappend(fc_tab->constraints, fc_newcon);
		}
	}
	else if (fc_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
	{
		PartitionDesc fc_pd = RelationGetPartitionDesc(fc_rel, true);
		Relation	fc_trigrel;

		/*
		 * 外键的触发器将在下面的循环中被多次操作。
		 * 为了避免重复打开/关闭触发器目录关系，我们在这里打开它并将其传递给下面调用的子例程。
		 */
		fc_trigrel = table_open(TriggerRelationId, RowExclusiveLock);

		/*
		 * 递归处理每个分区；要么找到一个现有的约束来重新归属到我们的，要么创建一个新的。
		 */
		for (int fc_i = 0; fc_i < fc_pd->nparts; fc_i++)
		{
			Oid			fc_partitionId = fc_pd->oids[fc_i];
			Relation	fc_partition = table_open(fc_partitionId, fc_lockmode);
			List	   *fc_partFKs;
			AttrMap    *fc_attmap;
			AttrNumber	fc_mapped_fkattnum[INDEX_MAX_KEYS];
			bool		fc_attached;
			ObjectAddress fc_address;
			ListCell   *fc_cell;

			fc_CheckAlterTableIsSafe(fc_partition);

			fc_attmap = build_attrmap_by_name(RelationGetDescr(fc_partition),
										   RelationGetDescr(fc_rel));
			for (int fc_j = 0; fc_j < fc_numfks; fc_j++)
				fc_mapped_fkattnum[fc_j] = fc_attmap->attnums[fc_fkattnum[fc_j] - 1];

			/* 检查是否可以重新利用现有的约束 */
			fc_partFKs = copyObject(RelationGetFKeyList(fc_partition));
			fc_attached = false;
			foreach(fc_cell, fc_partFKs)
			{
				ForeignKeyCacheInfo *fc_fk;

				fc_fk = lfirst_node(ForeignKeyCacheInfo, fc_cell);
				if (fc_tryAttachPartitionForeignKey(fc_fk,
												 fc_partitionId,
												 fc_parentConstr,
												 fc_numfks,
												 fc_mapped_fkattnum,
												 fc_pkattnum,
												 fc_pfeqoperators,
												 fc_insertTriggerOid,
												 fc_updateTriggerOid,
												 fc_trigrel))
				{
					fc_attached = true;
					break;
				}
			}
			if (fc_attached)
			{
				table_close(fc_partition, NoLock);
				continue;
			}

			/*
			 * 找不到可重用的良好约束；创建我们自己的。
			 */
			fc_address = fc_addFkConstraint(addFkReferencingSide,
									  fc_fkconstraint->conname, fc_fkconstraint,
									  fc_partition, fc_pkrel, fc_indexOid, fc_parentConstr,
									  fc_numfks, fc_pkattnum,
									  fc_mapped_fkattnum, fc_pfeqoperators,
									  fc_ppeqoperators, fc_ffeqoperators,
									  fc_numfkdelsetcols, fc_fkdelsetcols, true);

			/* 调用我们自己以完成创建，我们就完成了 */
			fc_addFkRecurseReferencing(fc_wqueue, fc_fkconstraint, fc_partition, fc_pkrel,
									fc_indexOid,
									fc_address.objectId,
									fc_numfks,
									fc_pkattnum,
									fc_mapped_fkattnum,
									fc_pfeqoperators,
									fc_ppeqoperators,
									fc_ffeqoperators,
									fc_numfkdelsetcols,
									fc_fkdelsetcols,
									fc_old_check_ok,
									fc_lockmode,
									fc_insertTriggerOid,
									fc_updateTriggerOid);

			table_close(fc_partition, NoLock);
		}

		table_close(fc_trigrel, RowExclusiveLock);
	}
}

/*
 * CloneForeignKeyConstraints
 *		从分区表克隆外键到新获得的分区。
 *
 * partitionRel 是 parentRel 的一个分区，因此我们可以确定它具有相同的列和相同的数据类型。
 * 不过，列的顺序可能不同。
 *
 * wqueue 必须传递以设置第 3 阶段的约束检查，除非已知引用方分区为空（例如在 CREATE TABLE /
 * PARTITION OF 中）。
 */
static void fc_CloneForeignKeyConstraints(List **fc_wqueue, Relation fc_parentRel,
						   Relation fc_partitionRel)
{
	/* 这仅适用于声明性分区 */
	Assert(fc_parentRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);

	/*
	 * 克隆父节点在被引用方的约束。
	 */
	fc_CloneFkReferenced(fc_parentRel, fc_partitionRel);

	/*
	 * 现在克隆父节点在引用方的约束。
	 */
	fc_CloneFkReferencing(fc_wqueue, fc_parentRel, fc_partitionRel);
}

/*
 * CloneFkReferenced
 *		CloneForeignKeyConstraints 的子例程
 *
 * 查找所有在被引用方的父关系上的外键；
 * 将这些约束克隆到给定的分区。当分区正在创建或附加时调用此例程。
 *
 * 这忽略自我引用的外键；这些将由 CloneFkReferencing 处理。
 *
 * 如果被附加的关系是分区的，这将递归到分区。
 * 通过调用 addFkRecurseReferenced 进行递归。
 */
static void fc_CloneFkReferenced(Relation fc_parentRel, Relation fc_partitionRel)
{
	Relation	fc_pg_constraint;
	AttrMap    *fc_attmap;
	ListCell   *fc_cell;
	SysScanDesc fc_scan;
	ScanKeyData fc_key[2];
	HeapTuple	fc_tuple;
	List	   *fc_clone = NIL;
	Relation	fc_trigrel;

	/*
	 * 搜索任何约束，其中此分区的父节点在被引用方。
	 * 然而，我们不能克隆任何父约束也将被克隆的约束，以避免重复。
	 * 因此，分两步进行：首先构建待克隆约束的列表，
	 * 然后遍历该列表，克隆那些父节点不在列表中的约束。
	 * （我们不能依赖先看到父节点，因为目录扫描可能首先返回子节点。）
	 */
	fc_pg_constraint = table_open(ConstraintRelationId, RowShareLock);
	ScanKeyInit(&fc_key[0],
				Anum_pg_constraint_confrelid, BTEqualStrategyNumber,
				F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(fc_parentRel)));
	ScanKeyInit(&fc_key[1],
				Anum_pg_constraint_contype, BTEqualStrategyNumber,
				F_CHAREQ, CharGetDatum(CONSTRAINT_FOREIGN));
	/* 这是一个顺序扫描，因为我们没有可用的索引... */
	fc_scan = systable_beginscan(fc_pg_constraint, InvalidOid, true,
							  NULL, 2, fc_key);
	while ((fc_tuple = systable_getnext(fc_scan)) != NULL)
	{
		Form_pg_constraint fc_constrForm = (Form_pg_constraint) GETSTRUCT(fc_tuple);

		fc_clone = lappend_oid(fc_clone, fc_constrForm->oid);
	}
	systable_endscan(fc_scan);
	table_close(fc_pg_constraint, RowShareLock);

	/*
	 * 外键的触发器将在下面的循环中被多次操作。
	 * 为了避免重复打开/关闭触发器目录关系，我们在这里打开它并将其传递给下面调用的子例程。
	 */
	fc_trigrel = table_open(TriggerRelationId, RowExclusiveLock);

	fc_attmap = build_attrmap_by_name(RelationGetDescr(fc_partitionRel),
								   RelationGetDescr(fc_parentRel));
	foreach(fc_cell, fc_clone)
	{
		Oid			fc_constrOid = lfirst_oid(fc_cell);
		Form_pg_constraint fc_constrForm;
		Relation	fc_fkRel;
		Oid			fc_indexOid;
		Oid			fc_partIndexId;
		int			fc_numfks;
		AttrNumber	fc_conkey[INDEX_MAX_KEYS];
		AttrNumber	fc_mapped_confkey[INDEX_MAX_KEYS];
		AttrNumber	fc_confkey[INDEX_MAX_KEYS];
		Oid			fc_conpfeqop[INDEX_MAX_KEYS];
		Oid			fc_conppeqop[INDEX_MAX_KEYS];
		Oid			fc_conffeqop[INDEX_MAX_KEYS];
		int			fc_numfkdelsetcols;
		AttrNumber	fc_confdelsetcols[INDEX_MAX_KEYS];
		Constraint *fc_fkconstraint;
		ObjectAddress fc_address;
		Oid			fc_deleteTriggerOid,
					fc_updateTriggerOid;

		fc_tuple = SearchSysCache1(CONSTROID, fc_constrOid);
		if (!HeapTupleIsValid(fc_tuple))
			elog(ERROR, "cache lookup failed for constraint %u", fc_constrOid);
		fc_constrForm = (Form_pg_constraint) GETSTRUCT(fc_tuple);

		/*
		 * 如上所述：不要尝试克隆我们将要克隆父约束的约束。
		 */
		if (list_member_oid(fc_clone, fc_constrForm->conparentid))
		{
			ReleaseSysCache(fc_tuple);
			continue;
		}

		/*
		 * 不要克隆自我引用的外键，这些外键可能在分区表中或即将分区中。
		 */
		if (fc_constrForm->conrelid == RelationGetRelid(fc_parentRel) ||
			fc_constrForm->conrelid == RelationGetRelid(fc_partitionRel))
		{
			ReleaseSysCache(fc_tuple);
			continue;
		}

		/* 我们需要与 CreateTrigger 将要获取的锁级别相同 */
		fc_fkRel = table_open(fc_constrForm->conrelid, ShareRowExclusiveLock);

		fc_indexOid = fc_constrForm->conindid;
		DeconstructFkConstraintRow(fc_tuple,
								   &fc_numfks,
								   fc_conkey,
								   fc_confkey,
								   fc_conpfeqop,
								   fc_conppeqop,
								   fc_conffeqop,
								   &fc_numfkdelsetcols,
								   fc_confdelsetcols);

		for (int fc_i = 0; fc_i < fc_numfks; fc_i++)
			fc_mapped_confkey[fc_i] = fc_attmap->attnums[fc_confkey[fc_i] - 1];

		fc_fkconstraint = makeNode(Constraint);
		fc_fkconstraint->contype = CONSTRAINT_FOREIGN;
		fc_fkconstraint->conname = NameStr(fc_constrForm->conname);
		fc_fkconstraint->deferrable = fc_constrForm->condeferrable;
		fc_fkconstraint->initdeferred = fc_constrForm->condeferred;
		fc_fkconstraint->location = -1;
		fc_fkconstraint->pktable = NULL;
		/* ->fk_attrs 在下面确定 */
		fc_fkconstraint->pk_attrs = NIL;
		fc_fkconstraint->fk_matchtype = fc_constrForm->confmatchtype;
		fc_fkconstraint->fk_upd_action = fc_constrForm->confupdtype;
		fc_fkconstraint->fk_del_action = fc_constrForm->confdeltype;
		fc_fkconstraint->fk_del_set_cols = NIL;
		fc_fkconstraint->old_conpfeqop = NIL;
		fc_fkconstraint->old_pktable_oid = InvalidOid;
		fc_fkconstraint->skip_validation = false;
		fc_fkconstraint->initially_valid = true;

		/* 设置用于生成约束名称的列名 */
		for (int fc_i = 0; fc_i < fc_numfks; fc_i++)
		{
			Form_pg_attribute fc_att;

			fc_att = TupleDescAttr(RelationGetDescr(fc_fkRel),
								fc_conkey[fc_i] - 1);
			fc_fkconstraint->fk_attrs = lappend(fc_fkconstraint->fk_attrs,
											 makeString(NameStr(fc_att->attname)));
		}

		/*
		 * 添加指向新分区的新外键约束。
		 * 由于这个新分区出现在约束的被引用方，
		 * 我们不需要为阶段3检查做准备。
		 */
		fc_partIndexId = index_get_partition(fc_partitionRel, fc_indexOid);
		if (!OidIsValid(fc_partIndexId))
			elog(ERROR, "index for %u not found in partition %s",
				 fc_indexOid, RelationGetRelationName(fc_partitionRel));

		/*
		 * 获取属于该约束的“动作”触发器，以作为在
		 * addFkRecurseReferenced()中创建的类似触发器的父OID。
		 */
		fc_GetForeignKeyActionTriggers(fc_trigrel, fc_constrOid,
									fc_constrForm->confrelid, fc_constrForm->conrelid,
									&fc_deleteTriggerOid, &fc_updateTriggerOid);

		/* 添加此约束... */
		fc_address = fc_addFkConstraint(addFkReferencedSide,
								  fc_fkconstraint->conname, fc_fkconstraint, fc_fkRel,
								  fc_partitionRel, fc_partIndexId, fc_constrOid,
								  fc_numfks, fc_mapped_confkey,
								  fc_conkey, fc_conpfeqop, fc_conppeqop, fc_conffeqop,
								  fc_numfkdelsetcols, fc_confdelsetcols, false);
		/* ...并递归 */
		fc_addFkRecurseReferenced(fc_fkconstraint,
							   fc_fkRel,
							   fc_partitionRel,
							   fc_partIndexId,
							   fc_address.objectId,
							   fc_numfks,
							   fc_mapped_confkey,
							   fc_conkey,
							   fc_conpfeqop,
							   fc_conppeqop,
							   fc_conffeqop,
							   fc_numfkdelsetcols,
							   fc_confdelsetcols,
							   true,
							   fc_deleteTriggerOid,
							   fc_updateTriggerOid);

		table_close(fc_fkRel, NoLock);
		ReleaseSysCache(fc_tuple);
	}

	table_close(fc_trigrel, RowExclusiveLock);
}

/*
 * CloneFkReferencing
 *		用于CloneForeignKeyConstraints的子例程
 *
 * 对于给定列表中父关系的每个FK约束，查找
 * 在其分区关系中可以重新归属的等效约束；
 * 如果找不到，则在分区中创建一个新约束作为其子约束。
 *
 * 如果提供了wqueue，则用于为每个
 * 克隆的约束设置阶段-3验证；如果不需要这样的验证，
 * 则省略它（例如：正在创建新的分区）。
 */
static void fc_CloneFkReferencing(List **fc_wqueue, Relation fc_parentRel, Relation fc_partRel)
{
	AttrMap    *fc_attmap;
	List	   *fc_partFKs;
	List	   *fc_clone = NIL;
	ListCell   *fc_cell;
	Relation	fc_trigrel;

	/* 获取需要克隆的约束列表 */
	foreach(fc_cell, RelationGetFKeyList(fc_parentRel))
	{
		ForeignKeyCacheInfo *fc_fk = lfirst(fc_cell);

		/*
		 * 拒绝附加一个作为分区的表，该分区表
		 * 已经有外键指向。 这个模式无用，
		 * 这一点已由用户并未对此提出投诉证明
		 * ，即创建一个引用分区的外键已经
		 * 不可能。这个限制使我们能够规避一些复杂性，
		 * 关于在ATTACH/DETACH期间需要的pg_constraint和pg_trigger行的创建。
		 */
		if (fc_fk->confrelid == RelationGetRelid(fc_partRel))
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("cannot attach table \"%s\" as a partition because it is referenced by foreign key \"%s\"",
							RelationGetRelationName(fc_partRel),
							get_constraint_name(fc_fk->conoid))));

		fc_clone = lappend_oid(fc_clone, fc_fk->conoid);
	}

	/*
	 * 如果没有任何事情要做则默默无视。特别是，这
	 * 避免为外部表引发虚假的错误。
	 */
	if (fc_clone == NIL)
		return;

	if (fc_partRel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("foreign key constraints are not supported on foreign tables")));

	/*
	 * 外键的触发器将在下面的循环中被多次操作。
	 * 为了避免重复打开/关闭触发器目录关系，我们在这里打开它并将其传递给下面调用的子例程。
	 */
	fc_trigrel = table_open(TriggerRelationId, RowExclusiveLock);

	/*
	 * 如果分区中的列不同，约束键可能不同。
	 * 此映射用于将它们转换。
	 */
	fc_attmap = build_attrmap_by_name(RelationGetDescr(fc_partRel),
								   RelationGetDescr(fc_parentRel));

	fc_partFKs = copyObject(RelationGetFKeyList(fc_partRel));

	foreach(fc_cell, fc_clone)
	{
		Oid			fc_parentConstrOid = lfirst_oid(fc_cell);
		Form_pg_constraint fc_constrForm;
		Relation	fc_pkrel;
		HeapTuple	fc_tuple;
		int			fc_numfks;
		AttrNumber	fc_conkey[INDEX_MAX_KEYS];
		AttrNumber	fc_mapped_conkey[INDEX_MAX_KEYS];
		AttrNumber	fc_confkey[INDEX_MAX_KEYS];
		Oid			fc_conpfeqop[INDEX_MAX_KEYS];
		Oid			fc_conppeqop[INDEX_MAX_KEYS];
		Oid			fc_conffeqop[INDEX_MAX_KEYS];
		int			fc_numfkdelsetcols;
		AttrNumber	fc_confdelsetcols[INDEX_MAX_KEYS];
		Constraint *fc_fkconstraint;
		bool		fc_attached;
		Oid			fc_indexOid;
		ObjectAddress fc_address;
		ListCell   *fc_cell;
		Oid			fc_insertTriggerOid,
					fc_updateTriggerOid;

		fc_tuple = SearchSysCache1(CONSTROID, fc_parentConstrOid);
		if (!HeapTupleIsValid(fc_tuple))
			elog(ERROR, "cache lookup failed for constraint %u",
				 fc_parentConstrOid);
		fc_constrForm = (Form_pg_constraint) GETSTRUCT(fc_tuple);

		/* 不要克隆其父对象正在被克隆的约束 */
		if (list_member_oid(fc_clone, fc_constrForm->conparentid))
		{
			ReleaseSysCache(fc_tuple);
			continue;
		}

		/*
		 * 需要防止并发删除。如果pkrel是一个分区
		 * 关系，这意味着要锁定所有分区。
		 */
		fc_pkrel = table_open(fc_constrForm->confrelid, ShareRowExclusiveLock);
		if (fc_pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
			(void) find_all_inheritors(RelationGetRelid(fc_pkrel),
									   ShareRowExclusiveLock, NULL);

		DeconstructFkConstraintRow(fc_tuple, &fc_numfks, fc_conkey, fc_confkey,
								   fc_conpfeqop, fc_conppeqop, fc_conffeqop,
								   &fc_numfkdelsetcols, fc_confdelsetcols);
		for (int fc_i = 0; fc_i < fc_numfks; fc_i++)
			fc_mapped_conkey[fc_i] = fc_attmap->attnums[fc_conkey[fc_i] - 1];

		/*
		 * 获取属于该约束的“检查”触发器，以作为
		 * 在addFkRecurseReferencing()中创建的类似触发器的父OID。
		 * 它们还传递给tryAttachPartitionForeignKey()，简单地
		 * 将作为父添加到分区现有的“检查”触发器中，
		 * 也就是说，如果相应的约束被认为可以附加到父约束。
		 */
		fc_GetForeignKeyCheckTriggers(fc_trigrel, fc_constrForm->oid,
								   fc_constrForm->confrelid, fc_constrForm->conrelid,
								   &fc_insertTriggerOid, &fc_updateTriggerOid);

		/*
		 * 在创建新约束之前，查看是否有任何现有的外键
		 * 适合这个目的。如果有，将父约束附加到
		 * 它，并且不要克隆任何东西。这样我们可以避免昂贵的
		 * 验证步骤，不会最终得到重复的外键，也
		 * 不需要对这个约束进行分区递归。
		 */
		fc_attached = false;
		foreach(fc_cell, fc_partFKs)
		{
			ForeignKeyCacheInfo *fc_fk = lfirst_node(ForeignKeyCacheInfo, fc_cell);

			if (fc_tryAttachPartitionForeignKey(fc_fk,
											 RelationGetRelid(fc_partRel),
											 fc_parentConstrOid,
											 fc_numfks,
											 fc_mapped_conkey,
											 fc_confkey,
											 fc_conpfeqop,
											 fc_insertTriggerOid,
											 fc_updateTriggerOid,
											 fc_trigrel))
			{
				fc_attached = true;
				table_close(fc_pkrel, NoLock);
				break;
			}
		}
		if (fc_attached)
		{
			ReleaseSysCache(fc_tuple);
			continue;
		}

		/* 不行。设置以创建我们自己的约束 */
		fc_fkconstraint = makeNode(Constraint);
		fc_fkconstraint->contype = CONSTRAINT_FOREIGN;
		/* ->conname 在下面确定 */
		fc_fkconstraint->deferrable = fc_constrForm->condeferrable;
		fc_fkconstraint->initdeferred = fc_constrForm->condeferred;
		fc_fkconstraint->location = -1;
		fc_fkconstraint->pktable = NULL;
		/* ->fk_attrs 在下面确定 */
		fc_fkconstraint->pk_attrs = NIL;
		fc_fkconstraint->fk_matchtype = fc_constrForm->confmatchtype;
		fc_fkconstraint->fk_upd_action = fc_constrForm->confupdtype;
		fc_fkconstraint->fk_del_action = fc_constrForm->confdeltype;
		fc_fkconstraint->fk_del_set_cols = NIL;
		fc_fkconstraint->old_conpfeqop = NIL;
		fc_fkconstraint->old_pktable_oid = InvalidOid;
		fc_fkconstraint->skip_validation = false;
		fc_fkconstraint->initially_valid = fc_constrForm->convalidated;
		for (int fc_i = 0; fc_i < fc_numfks; fc_i++)
		{
			Form_pg_attribute fc_att;

			fc_att = TupleDescAttr(RelationGetDescr(fc_partRel),
								fc_mapped_conkey[fc_i] - 1);
			fc_fkconstraint->fk_attrs = lappend(fc_fkconstraint->fk_attrs,
											 makeString(NameStr(fc_att->attname)));
		}

		fc_indexOid = fc_constrForm->conindid;

		/* 在这一层创建 pg_constraint 条目 */
		fc_address = fc_addFkConstraint(addFkReferencingSide,
								  NameStr(fc_constrForm->conname), fc_fkconstraint,
								  fc_partRel, fc_pkrel, fc_indexOid, fc_parentConstrOid,
								  fc_numfks, fc_confkey,
								  fc_mapped_conkey, fc_conpfeqop,
								  fc_conppeqop, fc_conffeqop,
								  fc_numfkdelsetcols, fc_confdelsetcols,
								  false);

		/* 完成克隆约束的元组 */
		ReleaseSysCache(fc_tuple);

		/* 创建检查触发器，如果有的话，对分区进行递归 */
		fc_addFkRecurseReferencing(fc_wqueue,
								fc_fkconstraint,
								fc_partRel,
								fc_pkrel,
								fc_indexOid,
								fc_address.objectId,
								fc_numfks,
								fc_confkey,
								fc_mapped_conkey,
								fc_conpfeqop,
								fc_conppeqop,
								fc_conffeqop,
								fc_numfkdelsetcols,
								fc_confdelsetcols,
								false,	/* 不存在旧检查 */
								AccessExclusiveLock,
								fc_insertTriggerOid,
								fc_updateTriggerOid);
		table_close(fc_pkrel, NoLock);
	}

	table_close(fc_trigrel, RowExclusiveLock);
}

/*
 * 当一个分区的父对象接收到[引用方的]外键时，
 * 我们必须将该外键传播到分区。然而，分区可能已经有
 * 一个等效的外键；这个例程比较给定的 ForeignKeyCacheInfo
 *（在分区中）与其他参数定义的外键。如果它们是等效的，
 * 创建两个约束之间的链接并返回真。
 *
 * 如果给定的外键与其他参数定义的外键不匹配，
 * 返回假。
 */
static bool fc_tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fc_fk,
							 Oid fc_partRelid,
							 Oid fc_parentConstrOid,
							 int fc_numfks,
							 AttrNumber *fc_mapped_conkey,
							 AttrNumber *fc_confkey,
							 Oid *fc_conpfeqop,
							 Oid fc_parentInsTrigger,
							 Oid fc_parentUpdTrigger,
							 Relation fc_trigrel)
{
	HeapTuple	fc_parentConstrTup;
	Form_pg_constraint fc_parentConstr;
	HeapTuple	fc_partcontup;
	Form_pg_constraint fc_partConstr;
	ScanKeyData fc_key;
	SysScanDesc fc_scan;
	HeapTuple	fc_trigtup;
	Oid			fc_insertTriggerOid,
				fc_updateTriggerOid;

	fc_parentConstrTup = SearchSysCache1(CONSTROID,
									  ObjectIdGetDatum(fc_parentConstrOid));
	if (!HeapTupleIsValid(fc_parentConstrTup))
		elog(ERROR, "cache lookup failed for constraint %u", fc_parentConstrOid);
	fc_parentConstr = (Form_pg_constraint) GETSTRUCT(fc_parentConstrTup);

	/*
	 * 做一些快速和简单的初步检查。如果这些检查中的任何一个失败，
	 * 我们不能使用这个约束。
	 */
	if (fc_fk->confrelid != fc_parentConstr->confrelid || fc_fk->nkeys != fc_numfks)
	{
		ReleaseSysCache(fc_parentConstrTup);
		return false;
	}
	for (int fc_i = 0; fc_i < fc_numfks; fc_i++)
	{
		if (fc_fk->conkey[fc_i] != fc_mapped_conkey[fc_i] ||
			fc_fk->confkey[fc_i] != fc_confkey[fc_i] ||
			fc_fk->conpfeqop[fc_i] != fc_conpfeqop[fc_i])
		{
			ReleaseSysCache(fc_parentConstrTup);
			return false;
		}
	}

	/*
	 * 到目前为止看起来不错；再做一些更详细的检查。
	 * 假设对于 'convalidated' 的检查可以省略，因为我们并不
	 * 真的关心这一点，但现在让我们小心一点。
	 */
	fc_partcontup = SearchSysCache1(CONSTROID,
								 ObjectIdGetDatum(fc_fk->conoid));
	if (!HeapTupleIsValid(fc_partcontup))
		elog(ERROR, "cache lookup failed for constraint %u", fc_fk->conoid);
	fc_partConstr = (Form_pg_constraint) GETSTRUCT(fc_partcontup);
	if (OidIsValid(fc_partConstr->conparentid) ||
		!fc_partConstr->convalidated ||
		fc_partConstr->condeferrable != fc_parentConstr->condeferrable ||
		fc_partConstr->condeferred != fc_parentConstr->condeferred ||
		fc_partConstr->confupdtype != fc_parentConstr->confupdtype ||
		fc_partConstr->confdeltype != fc_parentConstr->confdeltype ||
		fc_partConstr->confmatchtype != fc_parentConstr->confmatchtype)
	{
		ReleaseSysCache(fc_parentConstrTup);
		ReleaseSysCache(fc_partcontup);
		return false;
	}

	ReleaseSysCache(fc_partcontup);
	ReleaseSysCache(fc_parentConstrTup);

	/*
	 * 看起来不错！附加这个约束。新分区的动作触发器变得冗余——
	 * 父表已经有等效的触发器，这些触发器能够到达分区。
	 * 删除分区中的那些。我们通过它们有我们的约束
	 * OID 以及在引用的关系上来识别它们。
	 */
	ScanKeyInit(&fc_key,
				Anum_pg_trigger_tgconstraint,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_fk->conoid));
	fc_scan = systable_beginscan(fc_trigrel, TriggerConstraintIndexId, true,
							  NULL, 1, &fc_key);
	while ((fc_trigtup = systable_getnext(fc_scan)) != NULL)
	{
		Form_pg_trigger fc_trgform = (Form_pg_trigger) GETSTRUCT(fc_trigtup);
		ObjectAddress fc_trigger;

		if (fc_trgform->tgconstrrelid != fc_fk->conrelid)
			continue;
		if (fc_trgform->tgrelid != fc_fk->confrelid)
			continue;

		/*
		 * 这个约束最初是设置为包含这个触发器作为
		 * 实现对象的，因此存在一个依赖记录将
		 * 两者链接起来；然而，由于不再需要触发器，
		 * 我们移除依赖链接，以便能够在保持约束不变的
		 * 情况下删除触发器。
		 */
		deleteDependencyRecordsFor(TriggerRelationId,
								   fc_trgform->oid,
								   false);
		/* 使依赖删除对 performDeletion 可见 */
		CommandCounterIncrement();
		ObjectAddressSet(fc_trigger, TriggerRelationId,
						 fc_trgform->oid);
		performDeletion(&fc_trigger, DROP_RESTRICT, 0);
		/* 使触发器删除可见，以防循环迭代 */
		CommandCounterIncrement();
	}

	systable_endscan(fc_scan);

	ConstraintSetParentConstraint(fc_fk->conoid, fc_parentConstrOid, fc_partRelid);

	/*
	 * 像约束一样，将分区的“检查”触发器附加到
	 * 相应的父触发器。
	 */
	fc_GetForeignKeyCheckTriggers(fc_trigrel,
							   fc_fk->conoid, fc_fk->confrelid, fc_fk->conrelid,
							   &fc_insertTriggerOid, &fc_updateTriggerOid);
	Assert(OidIsValid(fc_insertTriggerOid) && OidIsValid(fc_parentInsTrigger));
	TriggerSetParentTrigger(fc_trigrel, fc_insertTriggerOid, fc_parentInsTrigger,
							fc_partRelid);
	Assert(OidIsValid(fc_updateTriggerOid) && OidIsValid(fc_parentUpdTrigger));
	TriggerSetParentTrigger(fc_trigrel, fc_updateTriggerOid, fc_parentUpdTrigger,
							fc_partRelid);

	/*
	 * 如果引用的表是分区的，那么我们现在附加的分区
	 * 具有额外的 pg_constraint 行和不再需要的动作触发器。
	 * 删除这些。
	 */
	if (get_rel_relkind(fc_fk->confrelid) == RELKIND_PARTITIONED_TABLE)
	{
		Relation	fc_pg_constraint = table_open(ConstraintRelationId, RowShareLock);
		ObjectAddresses *fc_objs;
		HeapTuple	fc_consttup;

		ScanKeyInit(&fc_key,
					Anum_pg_constraint_conrelid,
					BTEqualStrategyNumber, F_OIDEQ,
					ObjectIdGetDatum(fc_fk->conrelid));

		fc_scan = systable_beginscan(fc_pg_constraint,
								  ConstraintRelidTypidNameIndexId,
								  true, NULL, 1, &fc_key);
		fc_objs = new_object_addresses();
		while ((fc_consttup = systable_getnext(fc_scan)) != NULL)
		{
			Form_pg_constraint fc_conform = (Form_pg_constraint) GETSTRUCT(fc_consttup);

			if (fc_conform->conparentid != fc_fk->conoid)
				continue;
			else
			{
				ObjectAddress fc_addr;
				SysScanDesc fc_scan2;
				ScanKeyData fc_key2;
				int			fc_n PG_USED_FOR_ASSERTS_ONLY;

				ObjectAddressSet(fc_addr, ConstraintRelationId, fc_conform->oid);
				add_exact_object_address(&fc_addr, fc_objs);

				/*
				 * 首先，我们必须删除将
				 * 约束记录绑定在一起的依赖记录。
				 */
				fc_n = deleteDependencyRecordsForSpecific(ConstraintRelationId,
													   fc_conform->oid,
													   DEPENDENCY_INTERNAL,
													   ConstraintRelationId,
													   fc_fk->conoid);
				Assert(fc_n == 1); /* 实际上只期望有一个 */

				/*
				 * 现在搜索与此约束相关的触发器并设置
				 * 它们以进行删除
				 */
				ScanKeyInit(&fc_key2,
							Anum_pg_trigger_tgconstraint,
							BTEqualStrategyNumber, F_OIDEQ,
							ObjectIdGetDatum(fc_conform->oid));
				fc_scan2 = systable_beginscan(fc_trigrel, TriggerConstraintIndexId,
										   true, NULL, 1, &fc_key2);
				while ((fc_trigtup = systable_getnext(fc_scan2)) != NULL)
				{
					ObjectAddressSet(fc_addr, TriggerRelationId,
									 ((Form_pg_trigger) GETSTRUCT(fc_trigtup))->oid);
					add_exact_object_address(&fc_addr, fc_objs);
				}
				systable_endscan(fc_scan2);
			}
		}
		/* 使依赖删除可见 */
		CommandCounterIncrement();
		performMultipleDeletions(fc_objs, DROP_RESTRICT,
								 PERFORM_DELETION_INTERNAL);
		systable_endscan(fc_scan);

		table_close(fc_pg_constraint, RowShareLock);
	}

	CommandCounterIncrement();
	return true;
}

/*
 * GetForeignKeyActionTriggers
 * 		返回给定约束下的给定关系的删除和更新“动作”触发器
 */
static void fc_GetForeignKeyActionTriggers(Relation fc_trigrel,
							Oid fc_conoid, Oid fc_confrelid, Oid fc_conrelid,
							Oid *fc_deleteTriggerOid,
							Oid *fc_updateTriggerOid)
{
	ScanKeyData fc_key;
	SysScanDesc fc_scan;
	HeapTuple	fc_trigtup;

	*fc_deleteTriggerOid = *fc_updateTriggerOid = InvalidOid;
	ScanKeyInit(&fc_key,
				Anum_pg_trigger_tgconstraint,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_conoid));

	fc_scan = systable_beginscan(fc_trigrel, TriggerConstraintIndexId, true,
							  NULL, 1, &fc_key);
	while ((fc_trigtup = systable_getnext(fc_scan)) != NULL)
	{
		Form_pg_trigger fc_trgform = (Form_pg_trigger) GETSTRUCT(fc_trigtup);

		if (fc_trgform->tgconstrrelid != fc_conrelid)
			continue;
		if (fc_trgform->tgrelid != fc_confrelid)
			continue;
		/* 仅仅查看主键侧的“动作”触发器。 */
		if (RI_FKey_trigger_type(fc_trgform->tgfoid) != RI_TRIGGER_PK)
			continue;
		if (TRIGGER_FOR_DELETE(fc_trgform->tgtype))
		{
			Assert(*fc_deleteTriggerOid == InvalidOid);
			*fc_deleteTriggerOid = fc_trgform->oid;
		}
		else if (TRIGGER_FOR_UPDATE(fc_trgform->tgtype))
		{
			Assert(*fc_updateTriggerOid == InvalidOid);
			*fc_updateTriggerOid = fc_trgform->oid;
		}
#ifndef USE_ASSERT_CHECKING
		/* 在启用断言的构建中，继续查找以发现重复项 */
		if (OidIsValid(*fc_deleteTriggerOid) && OidIsValid(*fc_updateTriggerOid))
			break;
#endif
	}

	if (!OidIsValid(*fc_deleteTriggerOid))
		elog(ERROR, "could not find ON DELETE action trigger of foreign key constraint %u",
			 fc_conoid);
	if (!OidIsValid(*fc_updateTriggerOid))
		elog(ERROR, "could not find ON UPDATE action trigger of foreign key constraint %u",
			 fc_conoid);

	systable_endscan(fc_scan);
}

/*
 * GetForeignKeyCheckTriggers
 * 		返回给定约束下的给定关系的插入和更新“检查”触发器
 */
static void fc_GetForeignKeyCheckTriggers(Relation fc_trigrel,
						   Oid fc_conoid, Oid fc_confrelid, Oid fc_conrelid,
						   Oid *fc_insertTriggerOid,
						   Oid *fc_updateTriggerOid)
{
	ScanKeyData fc_key;
	SysScanDesc fc_scan;
	HeapTuple	fc_trigtup;

	*fc_insertTriggerOid = *fc_updateTriggerOid = InvalidOid;
	ScanKeyInit(&fc_key,
				Anum_pg_trigger_tgconstraint,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_conoid));

	fc_scan = systable_beginscan(fc_trigrel, TriggerConstraintIndexId, true,
							  NULL, 1, &fc_key);
	while ((fc_trigtup = systable_getnext(fc_scan)) != NULL)
	{
		Form_pg_trigger fc_trgform = (Form_pg_trigger) GETSTRUCT(fc_trigtup);

		if (fc_trgform->tgconstrrelid != fc_confrelid)
			continue;
		if (fc_trgform->tgrelid != fc_conrelid)
			continue;
		/* 仅仅查看外键侧的“检查”触发器。 */
		if (RI_FKey_trigger_type(fc_trgform->tgfoid) != RI_TRIGGER_FK)
			continue;
		if (TRIGGER_FOR_INSERT(fc_trgform->tgtype))
		{
			Assert(*fc_insertTriggerOid == InvalidOid);
			*fc_insertTriggerOid = fc_trgform->oid;
		}
		else if (TRIGGER_FOR_UPDATE(fc_trgform->tgtype))
		{
			Assert(*fc_updateTriggerOid == InvalidOid);
			*fc_updateTriggerOid = fc_trgform->oid;
		}
#ifndef USE_ASSERT_CHECKING
		/* 在启用断言的构建中，继续查找以发现重复项。 */
		if (OidIsValid(*fc_insertTriggerOid) && OidIsValid(*fc_updateTriggerOid))
			break;
#endif
	}

	if (!OidIsValid(*fc_insertTriggerOid))
		elog(ERROR, "could not find ON INSERT check triggers of foreign key constraint %u",
			 fc_conoid);
	if (!OidIsValid(*fc_updateTriggerOid))
		elog(ERROR, "could not find ON UPDATE check triggers of foreign key constraint %u",
			 fc_conoid);

	systable_endscan(fc_scan);
}

/*
 * ALTER TABLE ALTER CONSTRAINT
 *
 * 更新约束的属性。
 *
 * 目前仅适用于外键约束。
 *
 * 如果约束被修改，返回其地址；否则，返回
 * InvalidObjectAddress。
 */
static ObjectAddress fc_ATExecAlterConstraint(Relation fc_rel, AlterTableCmd *fc_cmd, bool fc_recurse,
					  bool fc_recursing, LOCKMODE fc_lockmode)
{
	Constraint *fc_cmdcon;
	Relation	fc_conrel;
	Relation	fc_tgrel;
	SysScanDesc fc_scan;
	ScanKeyData fc_skey[3];
	HeapTuple	fc_contuple;
	Form_pg_constraint fc_currcon;
	ObjectAddress fc_address;
	List	   *fc_otherrelids = NIL;
	ListCell   *fc_lc;

	fc_cmdcon = castNode(Constraint, fc_cmd->def);

	fc_conrel = table_open(ConstraintRelationId, RowExclusiveLock);
	fc_tgrel = table_open(TriggerRelationId, RowExclusiveLock);

	/*
	 * 查找并检查目标约束
	 */
	ScanKeyInit(&fc_skey[0],
				Anum_pg_constraint_conrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(RelationGetRelid(fc_rel)));
	ScanKeyInit(&fc_skey[1],
				Anum_pg_constraint_contypid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(InvalidOid));
	ScanKeyInit(&fc_skey[2],
				Anum_pg_constraint_conname,
				BTEqualStrategyNumber, F_NAMEEQ,
				CStringGetDatum(fc_cmdcon->conname));
	fc_scan = systable_beginscan(fc_conrel, ConstraintRelidTypidNameIndexId,
							  true, NULL, 3, fc_skey);

	/* 最多只能有一行匹配 */
	if (!HeapTupleIsValid(fc_contuple = systable_getnext(fc_scan)))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("constraint \"%s\" of relation \"%s\" does not exist",
						fc_cmdcon->conname, RelationGetRelationName(fc_rel))));

	fc_currcon = (Form_pg_constraint) GETSTRUCT(fc_contuple);
	if (fc_currcon->contype != CONSTRAINT_FOREIGN)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("constraint \"%s\" of relation \"%s\" is not a foreign key constraint",
						fc_cmdcon->conname, RelationGetRelationName(fc_rel))));

	/*
	 * 如果不是最上层的约束，抛出一个错误。
	 *
	 * 修改非最上层约束会使某些触发器保持不变，因为
	 * 它们并没有直接与该约束相连；此外，pg_dump会
	 * 忽略单个约束的延迟状态，因为它
	 * 仅转储最上层的约束。通过拒绝此操作并告知用户修改父约束来
	 * 避免这些问题。
	 */
	if (OidIsValid(fc_currcon->conparentid))
	{
		HeapTuple	fc_tp;
		Oid			fc_parent = fc_currcon->conparentid;
		char	   *fc_ancestorname = NULL;
		char	   *fc_ancestortable = NULL;

		/* 循环查找最上层的约束 */
		while (HeapTupleIsValid(fc_tp = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fc_parent))))
		{
			Form_pg_constraint fc_contup = (Form_pg_constraint) GETSTRUCT(fc_tp);

			/* 如果没有父项，这就是我们想要的约束 */
			if (!OidIsValid(fc_contup->conparentid))
			{
				fc_ancestorname = pstrdup(NameStr(fc_contup->conname));
				fc_ancestortable = get_rel_name(fc_contup->conrelid);
				ReleaseSysCache(fc_tp);
				break;
			}

			fc_parent = fc_contup->conparentid;
			ReleaseSysCache(fc_tp);
		}

		ereport(ERROR,
				(errmsg("cannot alter constraint \"%s\" on relation \"%s\"",
						fc_cmdcon->conname, RelationGetRelationName(fc_rel)),
				 fc_ancestorname && fc_ancestortable ?
				 errdetail("Constraint \"%s\" is derived from constraint \"%s\" of relation \"%s\".",
						   fc_cmdcon->conname, fc_ancestorname, fc_ancestortable) : 0,
				 errhint("You may alter the constraint it derives from, instead.")));
	}

	/*
	 * 进行实际的目录工作。如果已经处于
	 * 所需状态，可以跳过更改，但如果是分区表则不能：分区需要
	 * 被处理，以防它们在本地发生了约束的更改。
	 */
	fc_address = InvalidObjectAddress;
	if (fc_currcon->condeferrable != fc_cmdcon->deferrable ||
		fc_currcon->condeferred != fc_cmdcon->initdeferred ||
		fc_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
	{
		if (fc_ATExecAlterConstrRecurse(fc_cmdcon, fc_conrel, fc_tgrel, fc_rel, fc_contuple,
									 &fc_otherrelids, fc_lockmode))
			ObjectAddressSet(fc_address, ConstraintRelationId, fc_currcon->oid);
	}

	/*
	 * ATExecConstrRecurse已使具有约束本身的关系的relcache失效；
	 * 在这里，我们还使具有任何触发器的关系失效，这些触发器
	 * 是该约束的一部分。
	 */
	foreach(fc_lc, fc_otherrelids)
		CacheInvalidateRelcacheByRelid(lfirst_oid(fc_lc));

	systable_endscan(fc_scan);

	table_close(fc_tgrel, RowExclusiveLock);
	table_close(fc_conrel, RowExclusiveLock);

	return fc_address;
}

/*
 * ATExecAlterConstraint的递归子程序。如果约束被
 * 修改，则返回true。
 *
 * *otherrelids是包含受影响触发器的关系的OID的追加列表。
 *
 * 请注意，即便值是正确的，我们也必须递归，以防
 * 间接后代的约束在本地被修改。
 * （如果我们禁止修改分区中的约束，这可以避免，
 * 但是现有版本不这样做。）
 */
static bool fc_ATExecAlterConstrRecurse(Constraint *fc_cmdcon, Relation fc_conrel, Relation fc_tgrel,
						 Relation fc_rel, HeapTuple fc_contuple, List **fc_otherrelids,
						 LOCKMODE fc_lockmode)
{
	Form_pg_constraint fc_currcon;
	Oid			fc_conoid;
	Oid			fc_refrelid;
	bool		fc_changed = false;

	/* 由于这个函数递归，可能会导致栈溢出 */
	check_stack_depth();

	fc_currcon = (Form_pg_constraint) GETSTRUCT(fc_contuple);
	fc_conoid = fc_currcon->oid;
	fc_refrelid = fc_currcon->confrelid;

	/*
	 * 用cmdcon中的标志更新pg_constraint。
	 *
	 * 如果调用时要修改的约束已经处于所需状态，
	 * 则静默不做任何操作。
	 */
	if (fc_currcon->condeferrable != fc_cmdcon->deferrable ||
		fc_currcon->condeferred != fc_cmdcon->initdeferred)
	{
		HeapTuple	fc_copyTuple;
		Form_pg_constraint fc_copy_con;
		HeapTuple	fc_tgtuple;
		ScanKeyData fc_tgkey;
		SysScanDesc fc_tgscan;

		fc_copyTuple = heap_copytuple(fc_contuple);
		fc_copy_con = (Form_pg_constraint) GETSTRUCT(fc_copyTuple);
		fc_copy_con->condeferrable = fc_cmdcon->deferrable;
		fc_copy_con->condeferred = fc_cmdcon->initdeferred;
		CatalogTupleUpdate(fc_conrel, &fc_copyTuple->t_self, fc_copyTuple);

		InvokeObjectPostAlterHook(ConstraintRelationId,
								  fc_conoid, 0);

		heap_freetuple(fc_copyTuple);
		fc_changed = true;

		/* 使新的约束标志对其他人可见 */
		CacheInvalidateRelcache(fc_rel);

		/*
		 * 现在我们需要更新pg_trigger中的多个条目，这些条目
		 * 实现了约束。
		 */
		ScanKeyInit(&fc_tgkey,
					Anum_pg_trigger_tgconstraint,
					BTEqualStrategyNumber, F_OIDEQ,
					ObjectIdGetDatum(fc_conoid));
		fc_tgscan = systable_beginscan(fc_tgrel, TriggerConstraintIndexId, true,
									NULL, 1, &fc_tgkey);
		while (HeapTupleIsValid(fc_tgtuple = systable_getnext(fc_tgscan)))
		{
			Form_pg_trigger fc_tgform = (Form_pg_trigger) GETSTRUCT(fc_tgtuple);
			Form_pg_trigger fc_copy_tg;
			HeapTuple	fc_copyTuple;

			/*
			 * 记住涉及外键约束的其他关系的OID。
			 * （注意：我们可能可以跳过强制其他没有属性变化触发器的关系的relcache无效化，但我们要采取谨慎态度。）
			 */
			if (fc_tgform->tgrelid != RelationGetRelid(fc_rel))
				*fc_otherrelids = list_append_unique_oid(*fc_otherrelids,
													  fc_tgform->tgrelid);

			/*
			 * 更新RI_FKey_noaction_del、RI_FKey_noaction_upd、RI_FKey_check_ins和RI_FKey_check_upd触发器的延迟性，但不包括其他触发器；见createForeignKeyActionTriggers和CreateFKCheckTrigger。
			 */
			if (fc_tgform->tgfoid != F_RI_FKEY_NOACTION_DEL &&
				fc_tgform->tgfoid != F_RI_FKEY_NOACTION_UPD &&
				fc_tgform->tgfoid != F_RI_FKEY_CHECK_INS &&
				fc_tgform->tgfoid != F_RI_FKEY_CHECK_UPD)
				continue;

			fc_copyTuple = heap_copytuple(fc_tgtuple);
			fc_copy_tg = (Form_pg_trigger) GETSTRUCT(fc_copyTuple);

			fc_copy_tg->tgdeferrable = fc_cmdcon->deferrable;
			fc_copy_tg->tginitdeferred = fc_cmdcon->initdeferred;
			CatalogTupleUpdate(fc_tgrel, &fc_copyTuple->t_self, fc_copyTuple);

			InvokeObjectPostAlterHook(TriggerRelationId, fc_tgform->oid, 0);

			heap_freetuple(fc_copyTuple);
		}

		systable_endscan(fc_tgscan);
	}

	/*
	 * 如果约束两端的表是分区的，我们需要递归处理每个该约束的子约束。
	 *
	 * （这假定对于分区表强制设置了递归标志，而对于遗留继承未设置，尽管我们在这里没有检查。）
	 */
	if (fc_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
		get_rel_relkind(fc_refrelid) == RELKIND_PARTITIONED_TABLE)
	{
		ScanKeyData fc_pkey;
		SysScanDesc fc_pscan;
		HeapTuple	fc_childtup;

		ScanKeyInit(&fc_pkey,
					Anum_pg_constraint_conparentid,
					BTEqualStrategyNumber, F_OIDEQ,
					ObjectIdGetDatum(fc_conoid));

		fc_pscan = systable_beginscan(fc_conrel, ConstraintParentIndexId,
								   true, NULL, 1, &fc_pkey);

		while (HeapTupleIsValid(fc_childtup = systable_getnext(fc_pscan)))
		{
			Form_pg_constraint fc_childcon = (Form_pg_constraint) GETSTRUCT(fc_childtup);
			Relation	fc_childrel;

			fc_childrel = table_open(fc_childcon->conrelid, fc_lockmode);
			fc_ATExecAlterConstrRecurse(fc_cmdcon, fc_conrel, fc_tgrel, fc_childrel, fc_childtup,
									 fc_otherrelids, fc_lockmode);
			table_close(fc_childrel, NoLock);
		}

		systable_endscan(fc_pscan);
	}

	return fc_changed;
}

/*
 * ALTER TABLE VALIDATE CONSTRAINT
 *
 * XXX 我们在这里处理递归而不是在阶段1的原因是因为处理外键时没有好的办法跳过递归：在这种情况下不需要锁定子对象，但在那个级别我们无法避免这样做。
 *
 * 返回值是验证约束的地址。如果约束已经被验证，则返回InvalidObjectAddress。
 */
static ObjectAddress fc_ATExecValidateConstraint(List **fc_wqueue, Relation fc_rel, char *fc_constrName,
						 bool fc_recurse, bool fc_recursing, LOCKMODE fc_lockmode)
{
	Relation	fc_conrel;
	SysScanDesc fc_scan;
	ScanKeyData fc_skey[3];
	HeapTuple	fc_tuple;
	Form_pg_constraint fc_con;
	ObjectAddress fc_address;

	fc_conrel = table_open(ConstraintRelationId, RowExclusiveLock);

	/*
	 * 查找并检查目标约束
	 */
	ScanKeyInit(&fc_skey[0],
				Anum_pg_constraint_conrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(RelationGetRelid(fc_rel)));
	ScanKeyInit(&fc_skey[1],
				Anum_pg_constraint_contypid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(InvalidOid));
	ScanKeyInit(&fc_skey[2],
				Anum_pg_constraint_conname,
				BTEqualStrategyNumber, F_NAMEEQ,
				CStringGetDatum(fc_constrName));
	fc_scan = systable_beginscan(fc_conrel, ConstraintRelidTypidNameIndexId,
							  true, NULL, 3, fc_skey);

	/* 最多只能有一行匹配 */
	if (!HeapTupleIsValid(fc_tuple = systable_getnext(fc_scan)))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("constraint \"%s\" of relation \"%s\" does not exist",
						fc_constrName, RelationGetRelationName(fc_rel))));

	fc_con = (Form_pg_constraint) GETSTRUCT(fc_tuple);
	if (fc_con->contype != CONSTRAINT_FOREIGN &&
		fc_con->contype != CONSTRAINT_CHECK)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("constraint \"%s\" of relation \"%s\" is not a foreign key or check constraint",
						fc_constrName, RelationGetRelationName(fc_rel))));

	if (!fc_con->convalidated)
	{
		AlteredTableInfo *fc_tab;
		HeapTuple	fc_copyTuple;
		Form_pg_constraint fc_copy_con;

		if (fc_con->contype == CONSTRAINT_FOREIGN)
		{
			NewConstraint *fc_newcon;
			Constraint *fc_fkconstraint;

			/* 排队以进行阶段3验证 */
			fc_fkconstraint = makeNode(Constraint);
			/* 现在这就是我们所需的全部 */
			fc_fkconstraint->conname = fc_constrName;

			fc_newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
			fc_newcon->name = fc_constrName;
			fc_newcon->contype = CONSTR_FOREIGN;
			fc_newcon->refrelid = fc_con->confrelid;
			fc_newcon->refindid = fc_con->conindid;
			fc_newcon->conid = fc_con->oid;
			fc_newcon->qual = (Node *) fc_fkconstraint;

			/* 查找或创建该表的工作队列条目 */
			fc_tab = fc_ATGetQueueEntry(fc_wqueue, fc_rel);
			fc_tab->constraints = lappend(fc_tab->constraints, fc_newcon);

			/*
			 * 我们不允许创建无效的外键到或从分区表，因此忽略递归位是可以的。
			 */
		}
		else if (fc_con->contype == CONSTRAINT_CHECK)
		{
			List	   *fc_children = NIL;
			ListCell   *fc_child;
			NewConstraint *fc_newcon;
			bool		fc_isnull;
			Datum		fc_val;
			char	   *fc_conbin;

			/*
			 * 如果我们正在递归，父级已经完成了这项工作，因此跳过它。此外，如果约束是NO INHERIT约束，我们就不应在子级中寻找它。
			 */
			if (!fc_recursing && !fc_con->connoinherit)
				fc_children = find_all_inheritors(RelationGetRelid(fc_rel),
											   fc_lockmode, NULL);

			/*
			 * 对于CHECK约束，我们必须确保只有在子级已经验证的情况下才将约束标记为在父级上验证。
			 *
			 * 我们在验证父级之前递归，以减少死锁的风险。
			 */
			foreach(fc_child, fc_children)
			{
				Oid			fc_childoid = lfirst_oid(fc_child);
				Relation	fc_childrel;

				if (fc_childoid == RelationGetRelid(fc_rel))
					continue;

				/*
				 * 如果我们被告知不递归，那么就最好没有任何子表，因为我们无法将父级上的约束标记为有效，除非它对所有子表都是有效的。
				 */
				if (!fc_recurse)
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
							 errmsg("constraint must be validated on child tables too")));

				/* find_all_inheritors 已经获得锁 */
				fc_childrel = table_open(fc_childoid, NoLock);

				fc_ATExecValidateConstraint(fc_wqueue, fc_childrel, fc_constrName, false,
										 true, fc_lockmode);
				table_close(fc_childrel, NoLock);
			}

			/* 排队以进行阶段3验证 */
			fc_newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
			fc_newcon->name = fc_constrName;
			fc_newcon->contype = CONSTR_CHECK;
			fc_newcon->refrelid = InvalidOid;
			fc_newcon->refindid = InvalidOid;
			fc_newcon->conid = fc_con->oid;

			fc_val = SysCacheGetAttr(CONSTROID, fc_tuple,
								  Anum_pg_constraint_conbin, &fc_isnull);
			if (fc_isnull)
				elog(ERROR, "null conbin for constraint %u", fc_con->oid);

			fc_conbin = TextDatumGetCString(fc_val);
			fc_newcon->qual = (Node *) stringToNode(fc_conbin);

			/* 查找或创建该表的工作队列条目 */
			fc_tab = fc_ATGetQueueEntry(fc_wqueue, fc_rel);
			fc_tab->constraints = lappend(fc_tab->constraints, fc_newcon);

			/*
			 * 使relcache无效，以便其他人看到新的验证约束。
			 */
			CacheInvalidateRelcache(fc_rel);
		}

		/*
		 * 现在在我们打开门的时候更新目录。
		 */
		fc_copyTuple = heap_copytuple(fc_tuple);
		fc_copy_con = (Form_pg_constraint) GETSTRUCT(fc_copyTuple);
		fc_copy_con->convalidated = true;
		CatalogTupleUpdate(fc_conrel, &fc_copyTuple->t_self, fc_copyTuple);

		InvokeObjectPostAlterHook(ConstraintRelationId, fc_con->oid, 0);

		heap_freetuple(fc_copyTuple);

		ObjectAddressSet(fc_address, ConstraintRelationId, fc_con->oid);
	}
	else
		fc_address = InvalidObjectAddress; /* 已经验证 */

	systable_endscan(fc_scan);

	table_close(fc_conrel, RowExclusiveLock);

	return fc_address;
}


/*
 * transformColumnNameList - 转换列名列表
 *
 * 查找每个名称并返回其 attnum 和可选的类型 OID
 *
 * 注意：此函数的名称表明其是通用的，
 * 但实际上它仅用于查找出现在外键
 * 子句中的名称。 错误消息需要改进，以便在其他情况下使用它，
 * 也许有效性检查也是如此。
 */
static int fc_transformColumnNameList(Oid fc_relId, List *fc_colList,
						int16 *fc_attnums, Oid *fc_atttypids)
{
	ListCell   *fc_l;
	int			fc_attnum;

	fc_attnum = 0;
	foreach(fc_l, fc_colList)
	{
		char	   *fc_attname = strVal(lfirst(fc_l));
		HeapTuple	fc_atttuple;
		Form_pg_attribute fc_attform;

		fc_atttuple = SearchSysCacheAttName(fc_relId, fc_attname);
		if (!HeapTupleIsValid(fc_atttuple))
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_COLUMN),
					 errmsg("column \"%s\" referenced in foreign key constraint does not exist",
							fc_attname)));
		fc_attform = (Form_pg_attribute) GETSTRUCT(fc_atttuple);
		if (fc_attform->attnum < 0)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("system columns cannot be used in foreign keys")));
		if (fc_attnum >= INDEX_MAX_KEYS)
			ereport(ERROR,
					(errcode(ERRCODE_TOO_MANY_COLUMNS),
					 errmsg("cannot have more than %d keys in a foreign key",
							INDEX_MAX_KEYS)));
		fc_attnums[fc_attnum] = fc_attform->attnum;
		if (fc_atttypids != NULL)
			fc_atttypids[fc_attnum] = fc_attform->atttypid;
		ReleaseSysCache(fc_atttuple);
		fc_attnum++;
	}

	return fc_attnum;
}

/*
 * transformFkeyGetPrimaryKey -
 *
 * 查找 pkrel 的主键属性的名称、attnums 和类型。
 * 还返回支持主键的索引 OID 和索引操作类。
 *
 * 除 pkrel 外，所有参数都是输出参数。 此外，函数的
 * 返回值是主键中的属性数量。
 *
 * 在 REFERENCES 规范中省略列列表时使用。
 */
static int fc_transformFkeyGetPrimaryKey(Relation fc_pkrel, Oid *fc_indexOid,
						   List **fc_attnamelist,
						   int16 *fc_attnums, Oid *fc_atttypids,
						   Oid *fc_opclasses)
{
	List	   *fc_indexoidlist;
	ListCell   *fc_indexoidscan;
	HeapTuple	fc_indexTuple = NULL;
	Form_pg_index fc_indexStruct = NULL;
	Datum		fc_indclassDatum;
	bool		fc_isnull;
	oidvector  *fc_indclass;
	int			fc_i;

	/*
	 * 从 relcache 获取表的索引 OID 列表，并在 pg_index 系统缓存中查找
	 * 每个索引，直到找到一个标记为主键的索引
	 * （希望不会有超过一个这样的索引）。 也要确保它有效。
	 */
	*fc_indexOid = InvalidOid;

	fc_indexoidlist = RelationGetIndexList(fc_pkrel);

	foreach(fc_indexoidscan, fc_indexoidlist)
	{
		Oid			fc_indexoid = lfirst_oid(fc_indexoidscan);

		fc_indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(fc_indexoid));
		if (!HeapTupleIsValid(fc_indexTuple))
			elog(ERROR, "cache lookup failed for index %u", fc_indexoid);
		fc_indexStruct = (Form_pg_index) GETSTRUCT(fc_indexTuple);
		if (fc_indexStruct->indisprimary && fc_indexStruct->indisvalid)
		{
			/*
			 * 拒绝使用可延迟的主键。 这是根据 SQL 规范，
			 * 如果我们尝试允许它，将会出现许多有趣的语义问题。
			 */
			if (!fc_indexStruct->indimmediate)
				ereport(ERROR,
						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
						 errmsg("cannot use a deferrable primary key for referenced table \"%s\"",
								RelationGetRelationName(fc_pkrel))));

			*fc_indexOid = fc_indexoid;
			break;
		}
		ReleaseSysCache(fc_indexTuple);
	}

	list_free(fc_indexoidlist);

	/*
	 * 检查我们是否找到了它
	 */
	if (!OidIsValid(*fc_indexOid))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("there is no primary key for referenced table \"%s\"",
						RelationGetRelationName(fc_pkrel))));

	/* 必须以困难的方式获取 indclass */
	fc_indclassDatum = SysCacheGetAttr(INDEXRELID, fc_indexTuple,
									Anum_pg_index_indclass, &fc_isnull);
	Assert(!fc_isnull);
	fc_indclass = (oidvector *) DatumGetPointer(fc_indclassDatum);

	/*
	 * 现在从 indkey 定义中构建 PK 属性列表（我们
	 * 假设主键不能有表达式元素）
	 */
	*fc_attnamelist = NIL;
	for (fc_i = 0; fc_i < fc_indexStruct->indnkeyatts; fc_i++)
	{
		int			fc_pkattno = fc_indexStruct->indkey.values[fc_i];

		fc_attnums[fc_i] = fc_pkattno;
		fc_atttypids[fc_i] = attnumTypeId(fc_pkrel, fc_pkattno);
		fc_opclasses[fc_i] = fc_indclass->values[fc_i];
		*fc_attnamelist = lappend(*fc_attnamelist,
							   makeString(pstrdup(NameStr(*attnumAttName(fc_pkrel, fc_pkattno)))));
	}

	ReleaseSysCache(fc_indexTuple);

	return fc_i;
}

/*
 * transformFkeyCheckAttrs -
 *
 * 验证 'pkrel' 关系中的 'attnums' 列是否有效
 * 可以作为外键约束的一部分进行引用。
 *
 * 返回支持约束的唯一索引的 OID，并且
 * 用与索引列相关联的操作类填充调用者提供的 'opclasses' 数组。
 *
 * 在验证失败时引发错误。
 */
static Oid fc_transformFkeyCheckAttrs(Relation fc_pkrel,
						int fc_numattrs, int16 *fc_attnums,
						Oid *fc_opclasses)
{
	Oid			fc_indexoid = InvalidOid;
	bool		fc_found = false;
	bool		fc_found_deferrable = false;
	List	   *fc_indexoidlist;
	ListCell   *fc_indexoidscan;
	int			fc_i,
				fc_j;

	/*
	 * 拒绝在引用列列表中重复出现的列。
	 * 此类情况是 SQL 标准所禁止的，即使我们认为
	 * 允许它是有用的，但关于如何将列表与唯一索引
	 * 匹配也会存在歧义（特别是，哪个索引
	 * 操作类与哪个 FK 列有关也是不清楚的）。
	 */
	for (fc_i = 0; fc_i < fc_numattrs; fc_i++)
	{
		for (fc_j = fc_i + 1; fc_j < fc_numattrs; fc_j++)
		{
			if (fc_attnums[fc_i] == fc_attnums[fc_j])
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_FOREIGN_KEY),
						 errmsg("foreign key referenced-columns list must not contain duplicates")));
		}
	}

	/*
	 * 从 relcache 获取表的索引 OID 列表，并在 pg_index 系统缓存中查找
	 * 每个索引，并将唯一索引与我们所给的 attnums 列表进行匹配。
	 */
	fc_indexoidlist = RelationGetIndexList(fc_pkrel);

	foreach(fc_indexoidscan, fc_indexoidlist)
	{
		HeapTuple	fc_indexTuple;
		Form_pg_index fc_indexStruct;

		fc_indexoid = lfirst_oid(fc_indexoidscan);
		fc_indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(fc_indexoid));
		if (!HeapTupleIsValid(fc_indexTuple))
			elog(ERROR, "cache lookup failed for index %u", fc_indexoid);
		fc_indexStruct = (Form_pg_index) GETSTRUCT(fc_indexTuple);

		/*
		 * 必须拥有正确数量的列；必须唯一且不能是
		 * 部分索引；如果有任何表达式，也要放弃。
		 * 无效的索引也不行。
		 */
		if (fc_indexStruct->indnkeyatts == fc_numattrs &&
			fc_indexStruct->indisunique &&
			fc_indexStruct->indisvalid &&
			heap_attisnull(fc_indexTuple, Anum_pg_index_indpred, NULL) &&
			heap_attisnull(fc_indexTuple, Anum_pg_index_indexprs, NULL))
		{
			Datum		fc_indclassDatum;
			bool		fc_isnull;
			oidvector  *fc_indclass;

			/* 必须以困难的方式获取 indclass */
			fc_indclassDatum = SysCacheGetAttr(INDEXRELID, fc_indexTuple,
											Anum_pg_index_indclass, &fc_isnull);
			Assert(!fc_isnull);
			fc_indclass = (oidvector *) DatumGetPointer(fc_indclassDatum);

			/*
			 * 给定的 attnum 列表可以以任何顺序与索引列匹配。
			 * 检查是否匹配，并在此过程中提取适当的 opclasses。
			 *
			 * 我们知道 attnums[] 是无重复的，这在此函数开始时进行了测试，
			 * 并且我们在上面检查了索引列的数量是否一致，因此如果我们为每个 attnums[]
			 * 条目找到了一个匹配项，那么我们必须以某种顺序拥有一对一的匹配。
			 */
			for (fc_i = 0; fc_i < fc_numattrs; fc_i++)
			{
				fc_found = false;
				for (fc_j = 0; fc_j < fc_numattrs; fc_j++)
				{
					if (fc_attnums[fc_i] == fc_indexStruct->indkey.values[fc_j])
					{
						fc_opclasses[fc_i] = fc_indclass->values[fc_j];
						fc_found = true;
						break;
					}
				}
				if (!fc_found)
					break;
			}

			/*
			 * 拒绝使用可延迟的唯一/主键。这是根据 SQL 规范，
			 * 如果我们尝试允许它，会有许多有趣的语义问题。
			 */
			if (fc_found && !fc_indexStruct->indimmediate)
			{
				/*
				 * 记住我们找到一个匹配的索引，否则我们可以生成一个更合适的错误信息。
				 */
				fc_found_deferrable = true;
				fc_found = false;
			}
		}
		ReleaseSysCache(fc_indexTuple);
		if (fc_found)
			break;
	}

	if (!fc_found)
	{
		if (fc_found_deferrable)
			ereport(ERROR,
					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
					 errmsg("cannot use a deferrable unique constraint for referenced table \"%s\"",
							RelationGetRelationName(fc_pkrel))));
		else
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_FOREIGN_KEY),
					 errmsg("there is no unique constraint matching given keys for referenced table \"%s\"",
							RelationGetRelationName(fc_pkrel))));
	}

	list_free(fc_indexoidlist);

	return fc_indexoid;
}

/*
 * findFkeyCast -
 *
 *	包装在 ATAddForeignKeyConstraint() 的 find_coercion_pathway() 周围。
 *	调用者对二进制强制转换和精确匹配同样重视。
*/
static CoercionPathType
fc_findFkeyCast(Oid fc_targetTypeId, Oid fc_sourceTypeId, Oid *fc_funcid)
{
	CoercionPathType fc_ret;

	if (fc_targetTypeId == fc_sourceTypeId)
	{
		fc_ret = COERCION_PATH_RELABELTYPE;
		*fc_funcid = InvalidOid;
	}
	else
	{
		fc_ret = find_coercion_pathway(fc_targetTypeId, fc_sourceTypeId,
									COERCION_IMPLICIT, fc_funcid);
		if (fc_ret == COERCION_PATH_NONE)
			/* 一个先前依赖的强制转换现在不存在了。 */
			elog(ERROR, "could not find cast from %u to %u",
				 fc_sourceTypeId, fc_targetTypeId);
	}

	return fc_ret;
}

/*
 * 对 ADD FOREIGN KEY 的引用表进行权限检查
 *
 * 注意：我们已经检查过用户拥有引用表，
 * 否则我们早些时候就会失败；对此不需要额外检查。
 */
static void fc_checkFkeyPermissions(Relation fc_rel, int16 *fc_attnums, int fc_natts)
{
	Oid			fc_roleid = GetUserId();
	AclResult	fc_aclresult;
	int			fc_i;

	/* 如果我们对关系级别的 REFERENCES 权限没问题，那就可以了 */
	fc_aclresult = pg_class_aclcheck(RelationGetRelid(fc_rel), fc_roleid,
								  ACL_REFERENCES);
	if (fc_aclresult == ACLCHECK_OK)
		return;
	/* 否则我们必须对每一列拥有 REFERENCES 权限 */
	for (fc_i = 0; fc_i < fc_natts; fc_i++)
	{
		fc_aclresult = pg_attribute_aclcheck(RelationGetRelid(fc_rel), fc_attnums[fc_i],
										  fc_roleid, ACL_REFERENCES);
		if (fc_aclresult != ACLCHECK_OK)
			aclcheck_error(fc_aclresult, get_relkind_objtype(fc_rel->rd_rel->relkind),
						   RelationGetRelationName(fc_rel));
	}
}

/*
 * 扫描表中的现有行以验证它们是否满足建议的 FK 约束。
 *
 * 调用者必须适当地打开并锁定这两个关系。
 */
static void fc_validateForeignKeyConstraint(char *fc_conname,
							 Relation fc_rel,
							 Relation fc_pkrel,
							 Oid fc_pkindOid,
							 Oid fc_constraintOid)
{
	TupleTableSlot *fc_slot;
	TableScanDesc fc_scan;
	Trigger		fc_trig;
	Snapshot	fc_snapshot;
	MemoryContext fc_oldcxt;
	MemoryContext fc_perTupCxt;

	ereport(DEBUG1,
			(errmsg_internal("validating foreign key constraint \"%s\"", fc_conname)));

	/*
	 * 构建触发器调用结构；无论如何我们都需要它。
	 */
	MemSet(&fc_trig, 0, sizeof(fc_trig));
	fc_trig.tgoid = InvalidOid;
	fc_trig.tgname = fc_conname;
	fc_trig.tgenabled = TRIGGER_FIRES_ON_ORIGIN;
	fc_trig.tgisinternal = true;
	fc_trig.tgconstrrelid = RelationGetRelid(fc_pkrel);
	fc_trig.tgconstrindid = fc_pkindOid;
	fc_trig.tgconstraint = fc_constraintOid;
	fc_trig.tgdeferrable = false;
	fc_trig.tginitdeferred = false;
	/* 我们不需要填写剩余字段 */

	/*
	 * 查看我们是否可以通过单个 LEFT JOIN 查询完成此操作。
	 * 虚假的结果表明我们必须继续使用触发器方法。
	 */
	if (RI_Initial_Check(&fc_trig, fc_rel, fc_pkrel))
		return;

	/*
	 * 扫描每个元组，调用 RI_FKey_check_ins（插入触发器），
	 * 就好像该元组刚刚被插入一样。如果其中任何一个失败，它应该
	 * ereport(ERROR)，那就没什么了。
	 */
	fc_snapshot = RegisterSnapshot(GetLatestSnapshot());
	fc_slot = table_slot_create(fc_rel, NULL);
	fc_scan = table_beginscan(fc_rel, fc_snapshot, 0, NULL);

	fc_perTupCxt = AllocSetContextCreate(CurrentMemoryContext,
									  "validateForeignKeyConstraint",
									  ALLOCSET_SMALL_SIZES);
	fc_oldcxt = MemoryContextSwitchTo(fc_perTupCxt);

	while (table_scan_getnextslot(fc_scan, ForwardScanDirection, fc_slot))
	{
		LOCAL_FCINFO(fc_fcinfo, 0);
		TriggerData fc_trigdata = {0};

		CHECK_FOR_INTERRUPTS();

		/*
		 * 调用触发器函数
		 *
		 * 没有传递参数，但我们确实设置了上下文
		 */
		MemSet(fc_fcinfo, 0, SizeForFunctionCallInfo(0));

		/*
		 * 我们假设 RI_FKey_check_ins 不会查看 flinfo...
		 */
		fc_trigdata.type = T_TriggerData;
		fc_trigdata.tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW;
		fc_trigdata.tg_relation = fc_rel;
		fc_trigdata.tg_trigtuple = ExecFetchSlotHeapTuple(fc_slot, false, NULL);
		fc_trigdata.tg_trigslot = fc_slot;
		fc_trigdata.tg_trigger = &fc_trig;

		fc_fcinfo->context = (Node *) &fc_trigdata;

		RI_FKey_check_ins(fc_fcinfo);

		MemoryContextReset(fc_perTupCxt);
	}

	MemoryContextSwitchTo(fc_oldcxt);
	MemoryContextDelete(fc_perTupCxt);
	table_endscan(fc_scan);
	UnregisterSnapshot(fc_snapshot);
	ExecDropSingleTupleTableSlot(fc_slot);
}

/*
 * CreateFKCheckTrigger
 *		创建插入（on_insert=true）或更新“检查”触发器，
 *		实现给定的外键
 *
 * 返回所创建触发器的 OID。
 */
static Oid fc_CreateFKCheckTrigger(Oid fc_myRelOid, Oid fc_refRelOid, Constraint *fc_fkconstraint,
					 Oid fc_constraintOid, Oid fc_indexOid, Oid fc_parentTrigOid,
					 bool fc_on_insert)
{
	ObjectAddress fc_trigAddress;
	CreateTrigStmt *fc_fk_trigger;

	/*
	 * 注意：对于自引用的 FK（引用表和被引用表相同），
	 * ON UPDATE 操作在 CHECK 操作之前触发非常重要，
	 * 因为两个触发器在 UPDATE 事件期间会在同一行触发；
	 * 否则 CHECK 触发器将检查行的非最终状态。
	 * 触发器按名称顺序触发，因此我们通过
	 * 使用名称如“RI_ConstraintTrigger_a_NNNN”表示操作触发器
	 * 和“RI_ConstraintTrigger_c_NNNN”表示检查触发器来确保这一点。
	 */
	fc_fk_trigger = makeNode(CreateTrigStmt);
	fc_fk_trigger->replace = false;
	fc_fk_trigger->isconstraint = true;
	fc_fk_trigger->trigname = "RI_ConstraintTrigger_c";
	fc_fk_trigger->relation = NULL;

	/* 触发器是 ON INSERT 或 ON UPDATE */
	if (fc_on_insert)
	{
		fc_fk_trigger->funcname = SystemFuncName("RI_FKey_check_ins");
		fc_fk_trigger->events = TRIGGER_TYPE_INSERT;
	}
	else
	{
		fc_fk_trigger->funcname = SystemFuncName("RI_FKey_check_upd");
		fc_fk_trigger->events = TRIGGER_TYPE_UPDATE;
	}

	fc_fk_trigger->args = NIL;
	fc_fk_trigger->row = true;
	fc_fk_trigger->timing = TRIGGER_TYPE_AFTER;
	fc_fk_trigger->columns = NIL;
	fc_fk_trigger->whenClause = NULL;
	fc_fk_trigger->transitionRels = NIL;
	fc_fk_trigger->deferrable = fc_fkconstraint->deferrable;
	fc_fk_trigger->initdeferred = fc_fkconstraint->initdeferred;
	fc_fk_trigger->constrrel = NULL;

	fc_trigAddress = CreateTrigger(fc_fk_trigger, NULL, fc_myRelOid, fc_refRelOid,
								fc_constraintOid, fc_indexOid, InvalidOid,
								fc_parentTrigOid, NULL, true, false);

	/* 使到目前为止的更改可见 */
	CommandCounterIncrement();

	return fc_trigAddress.objectId;
}

/*
 * createForeignKeyActionTriggers
 *		创建引用方的 "动作" 触发器，以实现外键。
 *
 * 返回所创建触发器的 OID，在 *deleteTrigOid 和
 * *updateTrigOid 中。
 */
static void fc_createForeignKeyActionTriggers(Relation fc_rel, Oid fc_refRelOid, Constraint *fc_fkconstraint,
							   Oid fc_constraintOid, Oid fc_indexOid,
							   Oid fc_parentDelTrigger, Oid fc_parentUpdTrigger,
							   Oid *fc_deleteTrigOid, Oid *fc_updateTrigOid)
{
	CreateTrigStmt *fc_fk_trigger;
	ObjectAddress fc_trigAddress;

	/*
	 * 为引用表的 ON DELETE 动作构建并执行 CREATE CONSTRAINT TRIGGER 语句。
	 */
	fc_fk_trigger = makeNode(CreateTrigStmt);
	fc_fk_trigger->replace = false;
	fc_fk_trigger->isconstraint = true;
	fc_fk_trigger->trigname = "RI_ConstraintTrigger_a";
	fc_fk_trigger->relation = NULL;
	fc_fk_trigger->args = NIL;
	fc_fk_trigger->row = true;
	fc_fk_trigger->timing = TRIGGER_TYPE_AFTER;
	fc_fk_trigger->events = TRIGGER_TYPE_DELETE;
	fc_fk_trigger->columns = NIL;
	fc_fk_trigger->whenClause = NULL;
	fc_fk_trigger->transitionRels = NIL;
	fc_fk_trigger->constrrel = NULL;
	switch (fc_fkconstraint->fk_del_action)
	{
		case FKCONSTR_ACTION_NOACTION:
			fc_fk_trigger->deferrable = fc_fkconstraint->deferrable;
			fc_fk_trigger->initdeferred = fc_fkconstraint->initdeferred;
			fc_fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_del");
			break;
		case FKCONSTR_ACTION_RESTRICT:
			fc_fk_trigger->deferrable = false;
			fc_fk_trigger->initdeferred = false;
			fc_fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_del");
			break;
		case FKCONSTR_ACTION_CASCADE:
			fc_fk_trigger->deferrable = false;
			fc_fk_trigger->initdeferred = false;
			fc_fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_del");
			break;
		case FKCONSTR_ACTION_SETNULL:
			fc_fk_trigger->deferrable = false;
			fc_fk_trigger->initdeferred = false;
			fc_fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_del");
			break;
		case FKCONSTR_ACTION_SETDEFAULT:
			fc_fk_trigger->deferrable = false;
			fc_fk_trigger->initdeferred = false;
			fc_fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_del");
			break;
		default:
			elog(ERROR, "unrecognized FK action type: %d",
				 (int) fc_fkconstraint->fk_del_action);
			break;
	}

	fc_trigAddress = CreateTrigger(fc_fk_trigger, NULL, fc_refRelOid,
								RelationGetRelid(fc_rel),
								fc_constraintOid, fc_indexOid, InvalidOid,
								fc_parentDelTrigger, NULL, true, false);
	if (fc_deleteTrigOid)
		*fc_deleteTrigOid = fc_trigAddress.objectId;

	/* 使到目前为止的更改可见 */
	CommandCounterIncrement();

	/*
	 * 为引用表的 ON UPDATE 动作构建并执行 CREATE CONSTRAINT TRIGGER 语句。
	 */
	fc_fk_trigger = makeNode(CreateTrigStmt);
	fc_fk_trigger->replace = false;
	fc_fk_trigger->isconstraint = true;
	fc_fk_trigger->trigname = "RI_ConstraintTrigger_a";
	fc_fk_trigger->relation = NULL;
	fc_fk_trigger->args = NIL;
	fc_fk_trigger->row = true;
	fc_fk_trigger->timing = TRIGGER_TYPE_AFTER;
	fc_fk_trigger->events = TRIGGER_TYPE_UPDATE;
	fc_fk_trigger->columns = NIL;
	fc_fk_trigger->whenClause = NULL;
	fc_fk_trigger->transitionRels = NIL;
	fc_fk_trigger->constrrel = NULL;
	switch (fc_fkconstraint->fk_upd_action)
	{
		case FKCONSTR_ACTION_NOACTION:
			fc_fk_trigger->deferrable = fc_fkconstraint->deferrable;
			fc_fk_trigger->initdeferred = fc_fkconstraint->initdeferred;
			fc_fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_upd");
			break;
		case FKCONSTR_ACTION_RESTRICT:
			fc_fk_trigger->deferrable = false;
			fc_fk_trigger->initdeferred = false;
			fc_fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_upd");
			break;
		case FKCONSTR_ACTION_CASCADE:
			fc_fk_trigger->deferrable = false;
			fc_fk_trigger->initdeferred = false;
			fc_fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_upd");
			break;
		case FKCONSTR_ACTION_SETNULL:
			fc_fk_trigger->deferrable = false;
			fc_fk_trigger->initdeferred = false;
			fc_fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_upd");
			break;
		case FKCONSTR_ACTION_SETDEFAULT:
			fc_fk_trigger->deferrable = false;
			fc_fk_trigger->initdeferred = false;
			fc_fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_upd");
			break;
		default:
			elog(ERROR, "unrecognized FK action type: %d",
				 (int) fc_fkconstraint->fk_upd_action);
			break;
	}

	fc_trigAddress = CreateTrigger(fc_fk_trigger, NULL, fc_refRelOid,
								RelationGetRelid(fc_rel),
								fc_constraintOid, fc_indexOid, InvalidOid,
								fc_parentUpdTrigger, NULL, true, false);
	if (fc_updateTrigOid)
		*fc_updateTrigOid = fc_trigAddress.objectId;
}

/*
 * createForeignKeyCheckTriggers
 *		创建引用方的 "检查" 触发器，以实现外键。
 *
 * 返回所创建触发器的 OID，在 *insertTrigOid 和
 * *updateTrigOid 中。
 */
static void fc_createForeignKeyCheckTriggers(Oid fc_myRelOid, Oid fc_refRelOid,
							  Constraint *fc_fkconstraint, Oid fc_constraintOid,
							  Oid fc_indexOid,
							  Oid fc_parentInsTrigger, Oid fc_parentUpdTrigger,
							  Oid *fc_insertTrigOid, Oid *fc_updateTrigOid)
{
	*fc_insertTrigOid = fc_CreateFKCheckTrigger(fc_myRelOid, fc_refRelOid, fc_fkconstraint,
										  fc_constraintOid, fc_indexOid,
										  fc_parentInsTrigger, true);
	*fc_updateTrigOid = fc_CreateFKCheckTrigger(fc_myRelOid, fc_refRelOid, fc_fkconstraint,
										  fc_constraintOid, fc_indexOid,
										  fc_parentUpdTrigger, false);
}

/*
 * ALTER TABLE DROP CONSTRAINT
 *
 * 像 DROP COLUMN 一样，我们无法使用普通的 ALTER TABLE 递归机制。
 */
static void fc_ATExecDropConstraint(Relation fc_rel, const char *fc_constrName,
					 DropBehavior fc_behavior,
					 bool fc_recurse, bool fc_recursing,
					 bool fc_missing_ok, LOCKMODE fc_lockmode)
{
	List	   *fc_children;
	ListCell   *fc_child;
	Relation	fc_conrel;
	Form_pg_constraint fc_con;
	SysScanDesc fc_scan;
	ScanKeyData fc_skey[3];
	HeapTuple	fc_tuple;
	bool		fc_found = false;
	bool		fc_is_no_inherit_constraint = false;
	char		fc_contype;

	/* 由于这个函数递归，可能会导致栈溢出 */
	check_stack_depth();

	/* 在顶层，权限检查是在ATPrepCmd中进行的，否则进行检查 */
	if (fc_recursing)
		fc_ATSimplePermissions(AT_DropConstraint, fc_rel, ATT_TABLE | ATT_FOREIGN_TABLE);

	fc_conrel = table_open(ConstraintRelationId, RowExclusiveLock);

	/*
	 * 找到并删除目标约束
	 */
	ScanKeyInit(&fc_skey[0],
				Anum_pg_constraint_conrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(RelationGetRelid(fc_rel)));
	ScanKeyInit(&fc_skey[1],
				Anum_pg_constraint_contypid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(InvalidOid));
	ScanKeyInit(&fc_skey[2],
				Anum_pg_constraint_conname,
				BTEqualStrategyNumber, F_NAMEEQ,
				CStringGetDatum(fc_constrName));
	fc_scan = systable_beginscan(fc_conrel, ConstraintRelidTypidNameIndexId,
							  true, NULL, 3, fc_skey);

	/* 最多只能有一行匹配 */
	if (HeapTupleIsValid(fc_tuple = systable_getnext(fc_scan)))
	{
		ObjectAddress fc_conobj;

		fc_con = (Form_pg_constraint) GETSTRUCT(fc_tuple);

		/* 不要删除继承的约束 */
		if (fc_con->coninhcount > 0 && !fc_recursing)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
					 errmsg("cannot drop inherited constraint \"%s\" of relation \"%s\"",
							fc_constrName, RelationGetRelationName(fc_rel))));

		fc_is_no_inherit_constraint = fc_con->connoinherit;
		fc_contype = fc_con->contype;

		/*
		 * 如果这是一个外键约束，我们最好锁定被引用的
		 * 表并检查该表未被使用，就像我们已经为
		 * 约束表所做的一样（否则我们可能会删除具有未触发事件的触发器）。
		 * 但在自引用的情况下，我们可以/必须跳过这一点。
		 */
		if (fc_contype == CONSTRAINT_FOREIGN &&
			fc_con->confrelid != RelationGetRelid(fc_rel))
		{
			Relation	fc_frel;

			/* 必须匹配 RemoveTriggerById 处获得的锁： */
			fc_frel = table_open(fc_con->confrelid, AccessExclusiveLock);
			fc_CheckAlterTableIsSafe(fc_frel);
			table_close(fc_frel, NoLock);
		}

		/*
		 * 执行实际的约束删除
		 */
		fc_conobj.classId = ConstraintRelationId;
		fc_conobj.objectId = fc_con->oid;
		fc_conobj.objectSubId = 0;

		performDeletion(&fc_conobj, fc_behavior, 0);

		fc_found = true;
	}

	systable_endscan(fc_scan);

	if (!fc_found)
	{
		if (!fc_missing_ok)
		{
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_OBJECT),
					 errmsg("constraint \"%s\" of relation \"%s\" does not exist",
							fc_constrName, RelationGetRelationName(fc_rel))));
		}
		else
		{
			ereport(NOTICE,
					(errmsg("constraint \"%s\" of relation \"%s\" does not exist, skipping",
							fc_constrName, RelationGetRelationName(fc_rel))));
			table_close(fc_conrel, RowExclusiveLock);
			return;
		}
	}

	/*
	 * 对于分区表，非检查的继承约束通过依赖关系机制删除，
	 * 所以我们在这里完成了。
	 */
	if (fc_contype != CONSTRAINT_CHECK &&
		fc_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
	{
		table_close(fc_conrel, RowExclusiveLock);
		return;
	}

	/*
	 * 根据需要向子级传播。与大多数其他 ALTER
	 * 例程不同，我们必须一次进行一层递归；我们不能
	 * 使用 find_all_inheritors 一次性完成。
	 */
	if (!fc_is_no_inherit_constraint)
		fc_children = find_inheritance_children(RelationGetRelid(fc_rel), fc_lockmode);
	else
		fc_children = NIL;

	/*
	 * 对于分区表，如果存在分区并且被告知不进行
	 * 递归，那是用户错误。尤其是对于分区表，只有在父表上定义约束是没有意义的。
	 */
	if (fc_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
		fc_children != NIL && !fc_recurse)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
				 errmsg("cannot remove constraint from only the partitioned table when partitions exist"),
				 errhint("Do not specify the ONLY keyword.")));

	foreach(fc_child, fc_children)
	{
		Oid			fc_childrelid = lfirst_oid(fc_child);
		Relation	fc_childrel;
		HeapTuple	fc_copy_tuple;

		/* find_inheritance_children 已经获得锁定 */
		fc_childrel = table_open(fc_childrelid, NoLock);
		fc_CheckAlterTableIsSafe(fc_childrel);

		ScanKeyInit(&fc_skey[0],
					Anum_pg_constraint_conrelid,
					BTEqualStrategyNumber, F_OIDEQ,
					ObjectIdGetDatum(fc_childrelid));
		ScanKeyInit(&fc_skey[1],
					Anum_pg_constraint_contypid,
					BTEqualStrategyNumber, F_OIDEQ,
					ObjectIdGetDatum(InvalidOid));
		ScanKeyInit(&fc_skey[2],
					Anum_pg_constraint_conname,
					BTEqualStrategyNumber, F_NAMEEQ,
					CStringGetDatum(fc_constrName));
		fc_scan = systable_beginscan(fc_conrel, ConstraintRelidTypidNameIndexId,
								  true, NULL, 3, fc_skey);

		/* 最多只能有一行匹配 */
		if (!HeapTupleIsValid(fc_tuple = systable_getnext(fc_scan)))
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_OBJECT),
					 errmsg("constraint \"%s\" of relation \"%s\" does not exist",
							fc_constrName,
							RelationGetRelationName(fc_childrel))));

		fc_copy_tuple = heap_copytuple(fc_tuple);

		systable_endscan(fc_scan);

		fc_con = (Form_pg_constraint) GETSTRUCT(fc_copy_tuple);

		/* 目前只有 CHECK 约束可以被继承 */
		if (fc_con->contype != CONSTRAINT_CHECK)
			elog(ERROR, "inherited constraint is not a CHECK constraint");

		if (fc_con->coninhcount <= 0)	/* 不应该发生 */
			elog(ERROR, "relation %u has non-inherited constraint \"%s\"",
				 fc_childrelid, fc_constrName);

		if (fc_recurse)
		{
			/*
			 * 如果子约束有其他定义来源，则仅需
			 * 减少其继承计数；如果没有，则递归删除。
			 */
			if (fc_con->coninhcount == 1 && !fc_con->conislocal)
			{
				/* 也该删除这个子约束了 */
				fc_ATExecDropConstraint(fc_childrel, fc_constrName, fc_behavior,
									 true, true,
									 false, fc_lockmode);
			}
			else
			{
				/* 子约束必须在我删除后存活 */
				fc_con->coninhcount--;
				CatalogTupleUpdate(fc_conrel, &fc_copy_tuple->t_self, fc_copy_tuple);

				/* 使更新可见 */
				CommandCounterIncrement();
			}
		}
		else
		{
			/*
			 * 如果我们被告知仅在此表中删除（不递归），
			 * 我们需要将继承者的约束标记为局部定义
			 * 而不是继承的。
			 */
			fc_con->coninhcount--;
			fc_con->conislocal = true;

			CatalogTupleUpdate(fc_conrel, &fc_copy_tuple->t_self, fc_copy_tuple);

			/* 使更新可见 */
			CommandCounterIncrement();
		}

		heap_freetuple(fc_copy_tuple);

		table_close(fc_childrel, NoLock);
	}

	table_close(fc_conrel, RowExclusiveLock);
}

/*
 * ALTER COLUMN TYPE
 *
 * 与其他子命令类型不同，我们在阶段 1 中对 ALTER COLUMN TYPE 进行解析转换
 * 这里传入的 AlterTableCmd 已经经过转换（并且必须如此，因为我们依赖一些转换后的字段）。
 *
 * 这一点的目的是，在阶段 3 中将对一个表的所有 ALTER COLUMN TYPE 操作
 * "并行" 执行，因此所有的 USING 表达式应假设原始列类型进行解析。
 * 此外，这还允许 USING 表达式引用将被删除的字段。
 *
 * 为使这一切安全运行，AT_PASS_DROP 和 AT_PASS_ALTER_TYPE 必须是
 * 阶段 2 中的前两步执行步骤；它们必须不看到任何其他子命令类型的影响，
 * 因为 USING 表达式是针对未修改的表状态进行解析的。
 */
static void fc_ATPrepAlterColumnType(List **fc_wqueue,
					  AlteredTableInfo *fc_tab, Relation fc_rel,
					  bool fc_recurse, bool fc_recursing,
					  AlterTableCmd *fc_cmd, LOCKMODE fc_lockmode,
					  AlterTableUtilityContext *fc_context)
{
	char	   *fc_colName = fc_cmd->name;
	ColumnDef  *fc_def = (ColumnDef *) fc_cmd->def;
	TypeName   *fc_typeName = fc_def->typeName;
	Node	   *fc_transform = fc_def->cooked_default;
	HeapTuple	fc_tuple;
	Form_pg_attribute fc_attTup;
	AttrNumber	fc_attnum;
	Oid			fc_targettype;
	int32		fc_targettypmod;
	Oid			fc_targetcollid;
	NewColumnValue *fc_newval;
	ParseState *fc_pstate = make_parsestate(NULL);
	AclResult	fc_aclresult;
	bool		fc_is_expr;

	if (fc_rel->rd_rel->reloftype && !fc_recursing)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("cannot alter column type of typed table")));

	/* 查找属性以便我们检查继承状态 */
	fc_tuple = SearchSysCacheAttName(RelationGetRelid(fc_rel), fc_colName);
	if (!HeapTupleIsValid(fc_tuple))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_COLUMN),
				 errmsg("column \"%s\" of relation \"%s\" does not exist",
						fc_colName, RelationGetRelationName(fc_rel))));
	fc_attTup = (Form_pg_attribute) GETSTRUCT(fc_tuple);
	fc_attnum = fc_attTup->attnum;

	/* 不能更改系统属性 */
	if (fc_attnum <= 0)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot alter system column \"%s\"",
						fc_colName)));

	/*
	 * 在更改生成列的类型时，不能指定 USING，因为这将违反生成表达式。
	 */
	if (fc_attTup->attgenerated && fc_def->cooked_default)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
				 errmsg("cannot specify USING when altering type of generated column"),
				 errdetail("Column \"%s\" is a generated column.", fc_colName)));

	/*
	 * 不要更改继承的列。在外层级别，最好不要有任何继承定义；
	 * 在递归时，我们假设这在父级别上已经检查过（见下文）。
	 */
	if (fc_attTup->attinhcount > 0 && !fc_recursing)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
				 errmsg("cannot alter inherited column \"%s\"",
						fc_colName)));

	/* 不要更改用于分区键的列 */
	if (has_partition_attrs(fc_rel,
							bms_make_singleton(fc_attnum - FirstLowInvalidHeapAttributeNumber),
							&fc_is_expr))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
				 errmsg("cannot alter column \"%s\" because it is part of the partition key of relation \"%s\"",
						fc_colName, RelationGetRelationName(fc_rel))));

	/* 查找目标类型 */
	typenameTypeIdAndMod(NULL, fc_typeName, &fc_targettype, &fc_targettypmod);

	fc_aclresult = pg_type_aclcheck(fc_targettype, GetUserId(), ACL_USAGE);
	if (fc_aclresult != ACLCHECK_OK)
		aclcheck_error_type(fc_aclresult, fc_targettype);

	/* 以及排序规则 */
	fc_targetcollid = GetColumnDefCollation(NULL, fc_def, fc_targettype);

	/* 确保数据类型对于列是合法的 */
	CheckAttributeType(fc_colName, fc_targettype, fc_targetcollid,
					   list_make1_oid(fc_rel->rd_rel->reltype),
					   0);

	if (fc_tab->relkind == RELKIND_RELATION ||
		fc_tab->relkind == RELKIND_PARTITIONED_TABLE)
	{
		/*
		 * 设置一个表达式以将旧数据值转换为新类型。
		 * 如果给定了 USING 选项，则使用 transformAlterTableStmt 转换后的表达式，
		 * 否则只需采用旧值并尝试强制转换。
		 * 我们首先这样做，以便在浪费精力之前检测类型不兼容性，
		 * 并且因为我们需要表达式根据原始表行类型进行解析。
		 */
		if (!fc_transform)
		{
			fc_transform = (Node *) makeVar(1, fc_attnum,
										 fc_attTup->atttypid, fc_attTup->atttypmod,
										 fc_attTup->attcollation,
										 0);
		}

		fc_transform = coerce_to_target_type(fc_pstate,
										  fc_transform, exprType(fc_transform),
										  fc_targettype, fc_targettypmod,
										  COERCION_ASSIGNMENT,
										  COERCE_IMPLICIT_CAST,
										  -1);
		if (fc_transform == NULL)
		{
			/* 错误文本取决于是否指定了 USING */
			if (fc_def->cooked_default != NULL)
				ereport(ERROR,
						(errcode(ERRCODE_DATATYPE_MISMATCH),
						 errmsg("result of USING clause for column \"%s\""
								" cannot be cast automatically to type %s",
								fc_colName, format_type_be(fc_targettype)),
						 errhint("You might need to add an explicit cast.")));
			else
				ereport(ERROR,
						(errcode(ERRCODE_DATATYPE_MISMATCH),
						 errmsg("column \"%s\" cannot be cast automatically to type %s",
								fc_colName, format_type_be(fc_targettype)),
						 !fc_attTup->attgenerated ?
				/* 翻译者：USING 是 SQL，不要翻译它 */
						 errhint("You might need to specify \"USING %s::%s\".",
								 quote_identifier(fc_colName),
								 format_type_with_typemod(fc_targettype,
														  fc_targettypmod)) : 0));
		}

		/* 在其他所有操作后修复排序规则 */
		assign_expr_collations(fc_pstate, fc_transform);

		/* 现在规划表达式，以便我们能够准确评估重写的需要。 */
		fc_transform = (Node *) expression_planner((Expr *) fc_transform);

		/*
		 * 添加一个工作队列项目，以使 ATRewriteTable 更新列的内容。
		 */
		fc_newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue));
		fc_newval->attnum = fc_attnum;
		fc_newval->expr = (Expr *) fc_transform;
		fc_newval->is_generated = false;

		fc_tab->newvals = lappend(fc_tab->newvals, fc_newval);
		if (fc_ATColumnChangeRequiresRewrite(fc_transform, fc_attnum))
			fc_tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
	}
	else if (fc_transform)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("\"%s\" is not a table",
						RelationGetRelationName(fc_rel))));

	if (!RELKIND_HAS_STORAGE(fc_tab->relkind))
	{
		/*
		 * 对于没有存储的关系，现在执行此检查。
		 * 常规表将在稍后重写表时进行检查。
		 */
		find_composite_type_dependencies(fc_rel->rd_rel->reltype, fc_rel, NULL);
	}

	ReleaseSysCache(fc_tuple);

	/*
	 * 如有必要，手动递归，通过为每个子表排队一个新命令。
	 * 我们无法在此应用 ATSimpleRecursion，因为我们需要
	 * 在 USING 表达式中重新映射属性编号（如果有的话）。
	 *
	 * 如果我们被告知不进行递归，最好不要有任何子表；
	 * 否则，修改将使它们失去同步。
	 */
	if (fc_recurse)
	{
		Oid			fc_relid = RelationGetRelid(fc_rel);
		List	   *fc_child_oids,
				   *fc_child_numparents;
		ListCell   *fc_lo,
				   *fc_li;

		fc_child_oids = find_all_inheritors(fc_relid, fc_lockmode,
										 &fc_child_numparents);

		/*
		 * find_all_inheritors 执行继承层次的递归搜索，
		 * 所以我们要做的就是处理它返回的列表中的所有 relids。
		 */
		forboth(fc_lo, fc_child_oids, fc_li, fc_child_numparents)
		{
			Oid			fc_childrelid = lfirst_oid(fc_lo);
			int			fc_numparents = lfirst_int(fc_li);
			Relation	fc_childrel;
			HeapTuple	fc_childtuple;
			Form_pg_attribute fc_childattTup;

			if (fc_childrelid == fc_relid)
				continue;

			/* find_all_inheritors 已经获得锁 */
			fc_childrel = relation_open(fc_childrelid, NoLock);
			fc_CheckAlterTableIsSafe(fc_childrel);

			/*
			 * 验证子类是否没有来自此继承层次结构外的
			 * 此列的继承定义。
			 * （renameatt 做了类似的测试，但方式不同，
			 * 因为其递归机制不同。）
			 */
			fc_childtuple = SearchSysCacheAttName(RelationGetRelid(fc_childrel),
											   fc_colName);
			if (!HeapTupleIsValid(fc_childtuple))
				ereport(ERROR,
						(errcode(ERRCODE_UNDEFINED_COLUMN),
						 errmsg("column \"%s\" of relation \"%s\" does not exist",
								fc_colName, RelationGetRelationName(fc_childrel))));
			fc_childattTup = (Form_pg_attribute) GETSTRUCT(fc_childtuple);

			if (fc_childattTup->attinhcount > fc_numparents)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
						 errmsg("cannot alter inherited column \"%s\" of relation \"%s\"",
								fc_colName, RelationGetRelationName(fc_childrel))));

			ReleaseSysCache(fc_childtuple);

			/*
			 * 重新映射属性编号。如果未指定 USING 表达式，
			 * 则无需此步骤。
			 */
			if (fc_def->cooked_default)
			{
				AttrMap    *fc_attmap;
				bool		fc_found_whole_row;

				/* 创建一个副本以进行涂写 */
				fc_cmd = copyObject(fc_cmd);

				fc_attmap = build_attrmap_by_name(RelationGetDescr(fc_childrel),
											   RelationGetDescr(fc_rel));
				((ColumnDef *) fc_cmd->def)->cooked_default =
					map_variable_attnos(fc_def->cooked_default,
										1, 0,
										fc_attmap,
										InvalidOid, &fc_found_whole_row);
				if (fc_found_whole_row)
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("cannot convert whole-row table reference"),
							 errdetail("USING expression contains a whole-row table reference.")));
				pfree(fc_attmap);
			}
			fc_ATPrepCmd(fc_wqueue, fc_childrel, fc_cmd, false, true, fc_lockmode, fc_context);
			relation_close(fc_childrel, NoLock);
		}
	}
	else if (!fc_recursing &&
			 find_inheritance_children(RelationGetRelid(fc_rel), NoLock) != NIL)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
				 errmsg("type of inherited column \"%s\" must be changed in child tables too",
						fc_colName)));

	if (fc_tab->relkind == RELKIND_COMPOSITE_TYPE)
		fc_ATTypedTableRecursion(fc_wqueue, fc_rel, fc_cmd, fc_lockmode, fc_context);
}

/*
 * 当列的数据类型发生变化时，如果新类型与旧类型
 * 足够相似，并且 USING 子句没有尝试插入其他值，
 * 则可能不需要重写。在这些情况下跳过重写是安全的：
 *
 * - 旧类型可以二进制强制转换为新类型
 * - 新类型是旧类型的非约束域
 * - {NEW,OLD} 或 {OLD,NEW} 是 {timestamptz,timestamp} 且时区为 UTC
 *
 * 在受限域的情况下，我们可以通过扫描表并检查约束来处理，
 * 而不是实际重写，但我们当前并未尝试这样做。
 */
static bool fc_ATColumnChangeRequiresRewrite(Node *fc_expr, AttrNumber fc_varattno)
{
	Assert(fc_expr != NULL);

	for (;;)
	{
		/* 只有一个 varno，因此无需检查 */
		if (IsA(fc_expr, Var) && ((Var *) fc_expr)->varattno == fc_varattno)
			return false;
		else if (IsA(fc_expr, RelabelType))
			fc_expr = (Node *) ((RelabelType *) fc_expr)->arg;
		else if (IsA(fc_expr, CoerceToDomain))
		{
			CoerceToDomain *fc_d = (CoerceToDomain *) fc_expr;

			if (DomainHasConstraints(fc_d->resulttype))
				return true;
			fc_expr = (Node *) fc_d->arg;
		}
		else if (IsA(fc_expr, FuncExpr))
		{
			FuncExpr   *fc_f = (FuncExpr *) fc_expr;

			switch (fc_f->funcid)
			{
				case F_TIMESTAMPTZ_TIMESTAMP:
				case F_TIMESTAMP_TIMESTAMPTZ:
					if (TimestampTimestampTzRequiresRewrite())
						return true;
					else
						fc_expr = linitial(fc_f->args);
					break;
				default:
					return true;
			}
		}
		else
			return true;
	}
}

/*
 * ALTER COLUMN .. SET DATA TYPE
 *
 * 返回修改后的列的地址。
 */
static ObjectAddress fc_ATExecAlterColumnType(AlteredTableInfo *fc_tab, Relation fc_rel,
					  AlterTableCmd *fc_cmd, LOCKMODE fc_lockmode)
{
	char	   *fc_colName = fc_cmd->name;
	ColumnDef  *fc_def = (ColumnDef *) fc_cmd->def;
	TypeName   *fc_typeName = fc_def->typeName;
	HeapTuple	fc_heapTup;
	Form_pg_attribute fc_attTup,
				fc_attOldTup;
	AttrNumber	fc_attnum;
	HeapTuple	fc_typeTuple;
	Form_pg_type fc_tform;
	Oid			fc_targettype;
	int32		fc_targettypmod;
	Oid			fc_targetcollid;
	Node	   *fc_defaultexpr;
	Relation	fc_attrelation;
	Relation	fc_depRel;
	ScanKeyData fc_key[3];
	SysScanDesc fc_scan;
	HeapTuple	fc_depTup;
	ObjectAddress fc_address;

	/*
	 * 如果我们正在重写表，则清除所有缺失值，因为这
	 * 使它们毫无意义。
	 */
	if (fc_tab->rewrite)
	{
		Relation	fc_newrel;

		fc_newrel = table_open(RelationGetRelid(fc_rel), NoLock);
		RelationClearMissing(fc_newrel);
		relation_close(fc_newrel, NoLock);
		/* 确保我们不会与后续的属性修改冲突 */
		CommandCounterIncrement();
	}

	fc_attrelation = table_open(AttributeRelationId, RowExclusiveLock);

	/* 查找目标列 */
	fc_heapTup = SearchSysCacheCopyAttName(RelationGetRelid(fc_rel), fc_colName);
	if (!HeapTupleIsValid(fc_heapTup)) /* 不应该发生 */
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_COLUMN),
				 errmsg("column \"%s\" of relation \"%s\" does not exist",
						fc_colName, RelationGetRelationName(fc_rel))));
	fc_attTup = (Form_pg_attribute) GETSTRUCT(fc_heapTup);
	fc_attnum = fc_attTup->attnum;
	fc_attOldTup = TupleDescAttr(fc_tab->oldDesc, fc_attnum - 1);

	/* 检查同一列上的多个 ALTER TYPE --- 无法处理 */
	if (fc_attTup->atttypid != fc_attOldTup->atttypid ||
		fc_attTup->atttypmod != fc_attOldTup->atttypmod)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot alter type of column \"%s\" twice",
						fc_colName)));

	/* 查找目标类型（不应失败，因为预处理找到它） */
	fc_typeTuple = typenameType(NULL, fc_typeName, &fc_targettypmod);
	fc_tform = (Form_pg_type) GETSTRUCT(fc_typeTuple);
	fc_targettype = fc_tform->oid;
	/* 以及排序规则 */
	fc_targetcollid = GetColumnDefCollation(NULL, fc_def, fc_targettype);

	/*
	 * 如果列有默认表达式，则获取它并确保我们
	 * 可以将其强制转换为新数据类型。 （在更改
	 * 列类型之前必须这样做，因为 build_column_default
	 * 本身会试图进行强制转换，如果失败，将不会发出
	 * 我们想要的错误信息。）
	 *
	 * 我们在旧默认表达式的顶层移除任何隐式强制
	 * 转换步骤；这已达成一致以满足最小惊讶原则。
	 * （转换为新列类型应像是从用户所见的存储
	 * 表达式开始，而隐式强制转换不会被显示。）
	 */
	if (fc_attTup->atthasdef)
	{
		fc_defaultexpr = build_column_default(fc_rel, fc_attnum);
		Assert(fc_defaultexpr);
		fc_defaultexpr = strip_implicit_coercions(fc_defaultexpr);
		fc_defaultexpr = coerce_to_target_type(NULL,	/* 没有 UNKNOWN 参数 */
											fc_defaultexpr, exprType(fc_defaultexpr),
											fc_targettype, fc_targettypmod,
											COERCION_ASSIGNMENT,
											COERCE_IMPLICIT_CAST,
											-1);
		if (fc_defaultexpr == NULL)
		{
			if (fc_attTup->attgenerated)
				ereport(ERROR,
						(errcode(ERRCODE_DATATYPE_MISMATCH),
						 errmsg("generation expression for column \"%s\" cannot be cast automatically to type %s",
								fc_colName, format_type_be(fc_targettype))));
			else
				ereport(ERROR,
						(errcode(ERRCODE_DATATYPE_MISMATCH),
						 errmsg("default for column \"%s\" cannot be cast automatically to type %s",
								fc_colName, format_type_be(fc_targettype))));
		}
	}
	else
		fc_defaultexpr = NULL;

	/*
	 * 找到依赖于该列的所有内容（约束、索引等），
	 * 并记录足够的信息以让我们重新创建对象。
	 *
	 * 实际的重新创建并不在这里发生，而是仅在我们
	 * 执行所有单独的 ALTER TYPE 操作之后。
	 * 不过，我们必须在执行 ALTER TYPE 之前保存这些
	 * 信息，否则解析器会混淆。
	 */
	fc_depRel = table_open(DependRelationId, RowExclusiveLock);

	ScanKeyInit(&fc_key[0],
				Anum_pg_depend_refclassid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(RelationRelationId));
	ScanKeyInit(&fc_key[1],
				Anum_pg_depend_refobjid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(RelationGetRelid(fc_rel)));
	ScanKeyInit(&fc_key[2],
				Anum_pg_depend_refobjsubid,
				BTEqualStrategyNumber, F_INT4EQ,
				Int32GetDatum((int32) fc_attnum));

	fc_scan = systable_beginscan(fc_depRel, DependReferenceIndexId, true,
							  NULL, 3, fc_key);

	while (HeapTupleIsValid(fc_depTup = systable_getnext(fc_scan)))
	{
		Form_pg_depend fc_foundDep = (Form_pg_depend) GETSTRUCT(fc_depTup);
		ObjectAddress fc_foundObject;

		fc_foundObject.classId = fc_foundDep->classid;
		fc_foundObject.objectId = fc_foundDep->objid;
		fc_foundObject.objectSubId = fc_foundDep->objsubid;

		switch (getObjectClass(&fc_foundObject))
		{
			case OCLASS_CLASS:
				{
					char		fc_relKind = get_rel_relkind(fc_foundObject.objectId);

					if (fc_relKind == RELKIND_INDEX ||
						fc_relKind == RELKIND_PARTITIONED_INDEX)
					{
						Assert(fc_foundObject.objectSubId == 0);
						fc_RememberIndexForRebuilding(fc_foundObject.objectId, fc_tab);
					}
					else if (fc_relKind == RELKIND_SEQUENCE)
					{
						/*
						 * 这必须是 SERIAL 列的序列。我们不需要
						 * 对其进行任何操作。
						 */
						Assert(fc_foundObject.objectSubId == 0);
					}
					else
					{
						/* 不期望有其他直接依赖关系... */
						elog(ERROR, "unexpected object depending on column: %s",
							 getObjectDescription(&fc_foundObject, false));
					}
					break;
				}

			case OCLASS_CONSTRAINT:
				Assert(fc_foundObject.objectSubId == 0);
				fc_RememberConstraintForRebuilding(fc_foundObject.objectId, fc_tab);
				break;

			case OCLASS_PROC:

				/*
				 * 新式 SQL 函数可以依赖于一个列，如果该列在解析的函数体中被引用。理想情况下，我们会通过反解析和重新解析自动更新函数，但这很冒险，并且可能会失败。待修复。
				 */
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("cannot alter type of a column used by a function or procedure"),
						 errdetail("%s depends on column \"%s\"",
								   getObjectDescription(&fc_foundObject, false),
								   fc_colName)));
				break;

			case OCLASS_REWRITE:

				/*
				 * 视图/规则体与函数体几乎有相同的问题。待修复。
				 */
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("cannot alter type of a column used by a view or rule"),
						 errdetail("%s depends on column \"%s\"",
								   getObjectDescription(&fc_foundObject, false),
								   fc_colName)));
				break;

			case OCLASS_TRIGGER:

				/*
				 * 触发器可以依赖于一个列，因为该列被指定为更新目标，或者因为该列在触发器的 WHEN 条件中使用。第一种情况不需要额外的工作，但第二种情况需要更新 WHEN 表达式，这与上述问题相同。由于我们无法轻松判断适用哪个情况，我们暂时搁置两者。待修复。
				 */
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("cannot alter type of a column used in a trigger definition"),
						 errdetail("%s depends on column \"%s\"",
								   getObjectDescription(&fc_foundObject, false),
								   fc_colName)));
				break;

			case OCLASS_POLICY:

				/*
				 * 策略可以依赖于一个列，因为该列在策略的 USING 或 WITH CHECK 限定表达式中被指定。重写和重新检查策略表达式可能是可行的，但现在搁置。删除并重新创建策略肯定足够简单；不过，待修复。
				 */
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("cannot alter type of a column used in a policy definition"),
						 errdetail("%s depends on column \"%s\"",
								   getObjectDescription(&fc_foundObject, false),
								   fc_colName)));
				break;

			case OCLASS_DEFAULT:
				{
					ObjectAddress fc_col = GetAttrDefaultColumnAddress(fc_foundObject.objectId);

					if (fc_col.objectId == RelationGetRelid(fc_rel) &&
						fc_col.objectSubId == fc_attnum)
					{
						/*
						 * 忽略列自身的默认表达式，我们将在下面处理。
						 */
						Assert(fc_defaultexpr);
					}
					else
					{
						/*
						 * 这必须是来自同一表中其他生成列表达式的引用。更改由生成列使用的列的类型是 SQL 标准不允许的，因此现在暂时搁置。这可能需要一些思考和努力。
						 */
						ereport(ERROR,
								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
								 errmsg("cannot alter type of a column used by a generated column"),
								 errdetail("Column \"%s\" is used by generated column \"%s\".",
										   fc_colName,
										   get_attname(fc_col.objectId,
													   fc_col.objectSubId,
													   false))));
					}
					break;
				}

			case OCLASS_STATISTIC_EXT:

				/*
				 * 给扩展统计信息机制一个机会，修复此列类型更改可能破坏的任何东西。
				 */
				fc_RememberStatisticsForRebuilding(fc_foundObject.objectId, fc_tab);
				break;

			case OCLASS_PUBLICATION_REL:

				/*
				 * 在 PUBLICATION ... FOR TABLE ... WHERE 子句中引用列。与上述问题相同。待修复。
				 */
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("cannot alter type of a column used by a publication WHERE clause"),
						 errdetail("%s depends on column \"%s\"",
								   getObjectDescription(&fc_foundObject, false),
								   fc_colName)));
				break;

			case OCLASS_TYPE:
			case OCLASS_CAST:
			case OCLASS_COLLATION:
			case OCLASS_CONVERSION:
			case OCLASS_LANGUAGE:
			case OCLASS_LARGEOBJECT:
			case OCLASS_OPERATOR:
			case OCLASS_OPCLASS:
			case OCLASS_OPFAMILY:
			case OCLASS_AM:
			case OCLASS_AMOP:
			case OCLASS_AMPROC:
			case OCLASS_SCHEMA:
			case OCLASS_TSPARSER:
			case OCLASS_TSDICT:
			case OCLASS_TSTEMPLATE:
			case OCLASS_TSCONFIG:
			case OCLASS_ROLE:
			case OCLASS_DATABASE:
			case OCLASS_TBLSPACE:
			case OCLASS_FDW:
			case OCLASS_FOREIGN_SERVER:
			case OCLASS_USER_MAPPING:
			case OCLASS_DEFACL:
			case OCLASS_EXTENSION:
			case OCLASS_EVENT_TRIGGER:
			case OCLASS_PARAMETER_ACL:
			case OCLASS_PUBLICATION:
			case OCLASS_PUBLICATION_NAMESPACE:
			case OCLASS_SUBSCRIPTION:
			case OCLASS_TRANSFORM:

				/*
				 * 我们不期望这些对象依赖于一个列。
				 */
				elog(ERROR, "unexpected object depending on column: %s",
					 getObjectDescription(&fc_foundObject, false));
				break;

				/*
				 * 此处故意没有默认情况；我们希望编译器在上面没有处理新 OCLASS 时发出警告。
				 */
		}
	}

	systable_endscan(fc_scan);

	/*
	 * 现在扫描这个列对其他事物的依赖。我们应该发现的唯一事物是对列数据类型的依赖以及可能的排序依赖。这些可以被移除。
	 */
	ScanKeyInit(&fc_key[0],
				Anum_pg_depend_classid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(RelationRelationId));
	ScanKeyInit(&fc_key[1],
				Anum_pg_depend_objid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(RelationGetRelid(fc_rel)));
	ScanKeyInit(&fc_key[2],
				Anum_pg_depend_objsubid,
				BTEqualStrategyNumber, F_INT4EQ,
				Int32GetDatum((int32) fc_attnum));

	fc_scan = systable_beginscan(fc_depRel, DependDependerIndexId, true,
							  NULL, 3, fc_key);

	while (HeapTupleIsValid(fc_depTup = systable_getnext(fc_scan)))
	{
		Form_pg_depend fc_foundDep = (Form_pg_depend) GETSTRUCT(fc_depTup);
		ObjectAddress fc_foundObject;

		fc_foundObject.classId = fc_foundDep->refclassid;
		fc_foundObject.objectId = fc_foundDep->refobjid;
		fc_foundObject.objectSubId = fc_foundDep->refobjsubid;

		if (fc_foundDep->deptype != DEPENDENCY_NORMAL)
			elog(ERROR, "found unexpected dependency type '%c'",
				 fc_foundDep->deptype);
		if (!(fc_foundDep->refclassid == TypeRelationId &&
			  fc_foundDep->refobjid == fc_attTup->atttypid) &&
			!(fc_foundDep->refclassid == CollationRelationId &&
			  fc_foundDep->refobjid == fc_attTup->attcollation))
			elog(ERROR, "found unexpected dependency for column: %s",
				 getObjectDescription(&fc_foundObject, false));

		CatalogTupleDelete(fc_depRel, &fc_depTup->t_self);
	}

	systable_endscan(fc_scan);

	table_close(fc_depRel, RowExclusiveLock);

	/*
	 * 现在开始 --- 更改记录的列类型和排序。 （注意 heapTup 是 syscache 条目的副本，所以在上面写东西是可以的。）首先修复缺失的值（如果有的话）。
	 */
	if (fc_attTup->atthasmissing)
	{
		Datum		fc_missingval;
		bool		fc_missingNull;

		/* 如果重写为真，缺失值应该已经被清除 */
		Assert(fc_tab->rewrite == 0);

		/* 获取缺失值的数据项 */
		fc_missingval = heap_getattr(fc_heapTup,
								  Anum_pg_attribute_attmissingval,
								  fc_attrelation->rd_att,
								  &fc_missingNull);

		/* 如果是空数组，则无需操作 */

		if (!fc_missingNull)
		{
			/*
			 * 从数组中获取数据项，并将其重新打包到一个用新类型数据构建的新数组中。我们假设由于表不需要重写，因此实际的 Datum 不需要改变，仅需要更改数组元数据。
			 */

			int			fc_one = 1;
			bool		fc_isNull;
			Datum		fc_valuesAtt[Natts_pg_attribute];
			bool		fc_nullsAtt[Natts_pg_attribute];
			bool		fc_replacesAtt[Natts_pg_attribute];
			HeapTuple	fc_newTup;

			MemSet(fc_valuesAtt, 0, sizeof(fc_valuesAtt));
			MemSet(fc_nullsAtt, false, sizeof(fc_nullsAtt));
			MemSet(fc_replacesAtt, false, sizeof(fc_replacesAtt));

			fc_missingval = array_get_element(fc_missingval,
										   1,
										   &fc_one,
										   0,
										   fc_attTup->attlen,
										   fc_attTup->attbyval,
										   fc_attTup->attalign,
										   &fc_isNull);
			fc_missingval = PointerGetDatum(construct_array(&fc_missingval,
														 1,
														 fc_targettype,
														 fc_tform->typlen,
														 fc_tform->typbyval,
														 fc_tform->typalign));

			fc_valuesAtt[Anum_pg_attribute_attmissingval - 1] = fc_missingval;
			fc_replacesAtt[Anum_pg_attribute_attmissingval - 1] = true;
			fc_nullsAtt[Anum_pg_attribute_attmissingval - 1] = false;

			fc_newTup = heap_modify_tuple(fc_heapTup, RelationGetDescr(fc_attrelation),
									   fc_valuesAtt, fc_nullsAtt, fc_replacesAtt);
			heap_freetuple(fc_heapTup);
			fc_heapTup = fc_newTup;
			fc_attTup = (Form_pg_attribute) GETSTRUCT(fc_heapTup);
		}
	}

	fc_attTup->atttypid = fc_targettype;
	fc_attTup->atttypmod = fc_targettypmod;
	fc_attTup->attcollation = fc_targetcollid;
	fc_attTup->attndims = list_length(fc_typeName->arrayBounds);
	fc_attTup->attlen = fc_tform->typlen;
	fc_attTup->attbyval = fc_tform->typbyval;
	fc_attTup->attalign = fc_tform->typalign;
	fc_attTup->attstorage = fc_tform->typstorage;
	fc_attTup->attcompression = InvalidCompressionMethod;

	ReleaseSysCache(fc_typeTuple);

	CatalogTupleUpdate(fc_attrelation, &fc_heapTup->t_self, fc_heapTup);

	table_close(fc_attrelation, RowExclusiveLock);

	/* 在新数据类型和排序规则上安装依赖 */
	fc_add_column_datatype_dependency(RelationGetRelid(fc_rel), fc_attnum, fc_targettype);
	fc_add_column_collation_dependency(RelationGetRelid(fc_rel), fc_attnum, fc_targetcollid);

	/*
	 * 删除该列的任何 pg_statistic 条目，因为它现在是错误的类型
	 */
	RemoveStatistics(RelationGetRelid(fc_rel), fc_attnum);

	InvokeObjectPostAlterHook(RelationRelationId,
							  RelationGetRelid(fc_rel), fc_attnum);

	/*
	 * 如果存在，通过强力更新默认值 --- 删除并重新添加
	 * 默认值。采用捷径可能不安全，因为新版本
	 * 可能确实有额外的依赖关系。（此时做这件事是可以的，
	 * 而不是在其他 ALTER TYPE 命令之后，因为默认值不会
	 * 依赖于其他列类型。）
	 */
	if (fc_defaultexpr)
	{
		/*
		 * 如果它是一个 GENERATED 默认值，则删除其依赖记录，
		 * 特别是它对列的 INTERNAL 依赖，这样否则会导致
		 * dependency.c 拒绝执行删除。
		 */
		if (fc_attTup->attgenerated)
		{
			Oid			fc_attrdefoid = GetAttrDefaultOid(RelationGetRelid(fc_rel), fc_attnum);

			if (!OidIsValid(fc_attrdefoid))
				elog(ERROR, "could not find attrdef tuple for relation %u attnum %d",
					 RelationGetRelid(fc_rel), fc_attnum);
			(void) deleteDependencyRecordsFor(AttrDefaultRelationId, fc_attrdefoid, false);
		}

		/*
		 * 使迄今为止的更新可见，特别是将被再次更新的新
		 * pg_attribute 行。
		 */
		CommandCounterIncrement();

		/*
		 * 我们在这里使用 RESTRICT 出于安全考虑，但目前我们不期望
		 * 有任何东西依赖于默认值。
		 */
		RemoveAttrDefault(RelationGetRelid(fc_rel), fc_attnum, DROP_RESTRICT, true,
						  true);

		StoreAttrDefault(fc_rel, fc_attnum, fc_defaultexpr, true, false);
	}

	ObjectAddressSubSet(fc_address, RelationRelationId,
						RelationGetRelid(fc_rel), fc_attnum);

	/* 清理 */
	heap_freetuple(fc_heapTup);

	return fc_address;
}

/*
 * ATExecAlterColumnType 的子例程：记住需要重置副本身份。
 */
static void fc_RememberReplicaIdentityForRebuilding(Oid fc_indoid, AlteredTableInfo *fc_tab)
{
	if (!get_index_isreplident(fc_indoid))
		return;

	if (fc_tab->replicaIdentityIndex)
		elog(ERROR, "relation %u has multiple indexes marked as replica identity", fc_tab->relid);

	fc_tab->replicaIdentityIndex = get_rel_name(fc_indoid);
}

/*
 * ATExecAlterColumnType 的子例程：记住任何聚集索引。
 */
static void fc_RememberClusterOnForRebuilding(Oid fc_indoid, AlteredTableInfo *fc_tab)
{
	if (!get_index_isclustered(fc_indoid))
		return;

	if (fc_tab->clusterOnIndex)
		elog(ERROR, "relation %u has multiple clustered indexes", fc_tab->relid);

	fc_tab->clusterOnIndex = get_rel_name(fc_indoid);
}

/*
 * ATExecAlterColumnType 的子例程：记住需要重建约束（我们可能已经知道）。
 */
static void fc_RememberConstraintForRebuilding(Oid fc_conoid, AlteredTableInfo *fc_tab)
{
	/*
	 * 这项去重检查对于两个独立的原因至关重要：我们
	 * 不能尝试两次重建相同的约束，如果一个约束
	 * 依赖于多于一个要被修改类型的列，我们必须
	 * 在应用任何列类型更改之前捕获其定义字符串。
	 * 如果我们稍后再次询问，ruleutils.c 会感到困惑。
	 */
	if (!list_member_oid(fc_tab->changedConstraintOids, fc_conoid))
	{
		/* 好的，捕获约束的现有定义字符串 */
		char	   *fc_defstring = pg_get_constraintdef_command(fc_conoid);
		Oid			fc_indoid;

		fc_tab->changedConstraintOids = lappend_oid(fc_tab->changedConstraintOids,
												 fc_conoid);
		fc_tab->changedConstraintDefs = lappend(fc_tab->changedConstraintDefs,
											 fc_defstring);

		/*
		 * 对于约束的索引（如果有），记住它是用于
		 * 表的副本身份还是聚集索引，以便
		 * ATPostAlterTypeCleanup() 可以排队必要的命令以恢复
		 * 这些属性。
		 */
		fc_indoid = get_constraint_index(fc_conoid);
		if (OidIsValid(fc_indoid))
		{
			fc_RememberReplicaIdentityForRebuilding(fc_indoid, fc_tab);
			fc_RememberClusterOnForRebuilding(fc_indoid, fc_tab);
		}
	}
}

/*
 * ATExecAlterColumnType 的子例程：记住索引需要
 * 被重建（我们可能已经知道）。
 */
static void fc_RememberIndexForRebuilding(Oid fc_indoid, AlteredTableInfo *fc_tab)
{
	/*
	 * 这项去重检查对于两个独立的原因至关重要：我们
	 * 不能尝试两次重建相同的索引，如果一个索引
	 * 依赖于多于一个要被修改类型的列，我们必须捕获
	 * 其定义字符串在应用任何列类型更改之前。
	 * 如果我们稍后再次询问，ruleutils.c 会感到困惑。
	 */
	if (!list_member_oid(fc_tab->changedIndexOids, fc_indoid))
	{
		/*
		 * 在将其添加为待重建的索引之前，我们最好先看看它
		 * 是否属于一个约束，如果是的话则重建约束。
		 * 通常这个检查会失败，因为约束索引通常
		 * 只对其约束有依赖关系。但这样的索引也可能对
		 * 表列有直接依赖，例如带有部分排除约束的情况。
		 */
		Oid			fc_conoid = get_index_constraint(fc_indoid);

		if (OidIsValid(fc_conoid))
		{
			fc_RememberConstraintForRebuilding(fc_conoid, fc_tab);
		}
		else
		{
			/* 好的，捕获索引的现有定义字符串 */
			char	   *fc_defstring = pg_get_indexdef_string(fc_indoid);

			fc_tab->changedIndexOids = lappend_oid(fc_tab->changedIndexOids,
												fc_indoid);
			fc_tab->changedIndexDefs = lappend(fc_tab->changedIndexDefs,
											fc_defstring);

			/*
			 * 请记住，如果该索引用于表的副本标识
			 * 或者如果它是一个聚簇索引，以便 ATPostAlterTypeCleanup()
			 * 可以排队处理恢复这些属性所需的命令。
			 */
			fc_RememberReplicaIdentityForRebuilding(fc_indoid, fc_tab);
			fc_RememberClusterOnForRebuilding(fc_indoid, fc_tab);
		}
	}
}

/*
 * ATExecAlterColumnType 的子例程：请记住，需要重建统计对象
 * （我们可能已经知道）。
 */
static void fc_RememberStatisticsForRebuilding(Oid fc_stxoid, AlteredTableInfo *fc_tab)
{
	/*
	 * 这个去重检查对于两个独立的原因是至关重要的：我们
	 * 不能试图两次重新创建相同的统计对象，并且如果统计对象
	 * 依赖于多个列，且这些列的类型需要被修改，我们必须在
	 * 应用任何类型更改之前捕获其定义字符串。 如果稍后再次询问，
	 * ruleutils.c 将会感到困惑。
	 */
	if (!list_member_oid(fc_tab->changedStatisticsOids, fc_stxoid))
	{
		/* 好的，捕获统计对象的现有定义字符串 */
		char	   *fc_defstring = pg_get_statisticsobjdef_string(fc_stxoid);

		fc_tab->changedStatisticsOids = lappend_oid(fc_tab->changedStatisticsOids,
												 fc_stxoid);
		fc_tab->changedStatisticsDefs = lappend(fc_tab->changedStatisticsDefs,
											 fc_defstring);
	}
}

/*
 * 在我们完成特定关系的所有 ALTER TYPE 操作之后进行清理。
 * 我们必须删除并重新创建所有依赖于被修改列的索引和约束。
 * 我们在这里实际执行删除，但重新创建通过添加工作
 * 队列条目的方式在稍后处理这些步骤。
 */
static void fc_ATPostAlterTypeCleanup(List **fc_wqueue, AlteredTableInfo *fc_tab, LOCKMODE fc_lockmode)
{
	ObjectAddress fc_obj;
	ObjectAddresses *fc_objects;
	ListCell   *fc_def_item;
	ListCell   *fc_oid_item;

	/*
	 * 收集所有要删除的约束和索引，以便我们可以在
	 * 一个单一的调用中处理它们。 这样我们就不必担心它们之间的
	 * 依赖关系。
	 */
	fc_objects = new_object_addresses();

	/*
	 * 重新解析索引和约束的定义，并将其附加到
	 * 相应的工作队列条目上。 我们在删除之前进行此操作，
	 * 因为在外键约束的情况下，我们可能还没有对
	 * 该约束附加的表获得独占锁，且我们需要在重新解析/删除之前
	 * 获得这一点。
	 *
	 * 我们不能依赖反解析的输出来告诉我们操作哪个关系，
	 * 因为并发活动可能使名称解析不同。 相反，我们必须使用
	 * 我们正在处理的约束或索引的 OID 来确定操作哪个关系。
	 */
	forboth(fc_oid_item, fc_tab->changedConstraintOids,
			fc_def_item, fc_tab->changedConstraintDefs)
	{
		Oid			fc_oldId = lfirst_oid(fc_oid_item);
		HeapTuple	fc_tup;
		Form_pg_constraint fc_con;
		Oid			fc_relid;
		Oid			fc_confrelid;
		char		fc_contype;
		bool		fc_conislocal;

		fc_tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fc_oldId));
		if (!HeapTupleIsValid(fc_tup)) /* 不应该发生 */
			elog(ERROR, "cache lookup failed for constraint %u", fc_oldId);
		fc_con = (Form_pg_constraint) GETSTRUCT(fc_tup);
		if (OidIsValid(fc_con->conrelid))
			fc_relid = fc_con->conrelid;
		else
		{
			/* 一定是一个域约束 */
			fc_relid = get_typ_typrelid(getBaseType(fc_con->contypid));
			if (!OidIsValid(fc_relid))
				elog(ERROR, "could not identify relation associated with constraint %u", fc_oldId);
		}
		fc_confrelid = fc_con->confrelid;
		fc_contype = fc_con->contype;
		fc_conislocal = fc_con->conislocal;
		ReleaseSysCache(fc_tup);

		ObjectAddressSet(fc_obj, ConstraintRelationId, fc_oldId);
		add_exact_object_address(&fc_obj, fc_objects);

		/*
		 * 如果约束是继承的（仅），我们不想在这里注入一个
		 * 新的定义；它将在 ATAddCheckConstraint 从添加
		 * 父表的约束时重新创建。但我们必须将信息携带到这里，
		 * 以便我们可以在下面删除该约束。
		 */
		if (!fc_conislocal)
			continue;

		/*
		 * 在重建引用我们正在修改的表的外键约束时，
		 * 我们可能尚未对外键的表获得任何锁，因此现在获取
		 * 一把。我们将在 DROP CONSTRAINT 步骤中需要 AccessExclusiveLock，
		 * 因此请求任何较弱的锁没有意义。
		 */
		if (fc_relid != fc_tab->relid && fc_contype == CONSTRAINT_FOREIGN)
			LockRelationOid(fc_relid, AccessExclusiveLock);

		fc_ATPostAlterTypeParse(fc_oldId, fc_relid, fc_confrelid,
							 (char *) lfirst(fc_def_item),
							 fc_wqueue, fc_lockmode, fc_tab->rewrite);
	}
	forboth(fc_oid_item, fc_tab->changedIndexOids,
			fc_def_item, fc_tab->changedIndexDefs)
	{
		Oid			fc_oldId = lfirst_oid(fc_oid_item);
		Oid			fc_relid;

		fc_relid = IndexGetRelation(fc_oldId, false);
		fc_ATPostAlterTypeParse(fc_oldId, fc_relid, InvalidOid,
							 (char *) lfirst(fc_def_item),
							 fc_wqueue, fc_lockmode, fc_tab->rewrite);

		ObjectAddressSet(fc_obj, RelationRelationId, fc_oldId);
		add_exact_object_address(&fc_obj, fc_objects);
	}

	/* 为新的统计信息添加依赖关系 */
	forboth(fc_oid_item, fc_tab->changedStatisticsOids,
			fc_def_item, fc_tab->changedStatisticsDefs)
	{
		Oid			fc_oldId = lfirst_oid(fc_oid_item);
		Oid			fc_relid;

		fc_relid = StatisticsGetRelation(fc_oldId, false);
		fc_ATPostAlterTypeParse(fc_oldId, fc_relid, InvalidOid,
							 (char *) lfirst(fc_def_item),
							 fc_wqueue, fc_lockmode, fc_tab->rewrite);

		ObjectAddressSet(fc_obj, StatisticExtRelationId, fc_oldId);
		add_exact_object_address(&fc_obj, fc_objects);
	}

	/*
	 * 排队命令以恢复副本标识索引标记
	 */
	if (fc_tab->replicaIdentityIndex)
	{
		AlterTableCmd *fc_cmd = makeNode(AlterTableCmd);
		ReplicaIdentityStmt *fc_subcmd = makeNode(ReplicaIdentityStmt);

		fc_subcmd->identity_type = REPLICA_IDENTITY_INDEX;
		fc_subcmd->name = fc_tab->replicaIdentityIndex;
		fc_cmd->subtype = AT_ReplicaIdentity;
		fc_cmd->def = (Node *) fc_subcmd;

		/* 在索引和约束之后执行 */
		fc_tab->subcmds[AT_PASS_OLD_CONSTR] =
			lappend(fc_tab->subcmds[AT_PASS_OLD_CONSTR], fc_cmd);
	}

	/*
	 * 排队命令以恢复用于聚类的索引的标记。
	 */
	if (fc_tab->clusterOnIndex)
	{
		AlterTableCmd *fc_cmd = makeNode(AlterTableCmd);

		fc_cmd->subtype = AT_ClusterOn;
		fc_cmd->name = fc_tab->clusterOnIndex;

		/* 在索引和约束之后执行 */
		fc_tab->subcmds[AT_PASS_OLD_CONSTR] =
			lappend(fc_tab->subcmds[AT_PASS_OLD_CONSTR], fc_cmd);
	}

	/*
	 * 在这里使用 DROP_RESTRICT 应该没问题，因为其他地方不应该依赖这些对象。
	 */
	performMultipleDeletions(fc_objects, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);

	free_object_addresses(fc_objects);

	/*
	 * 这些对象将在后续的工作队列处理过程中被重新创建。
	 */
}

/*
 * 解析之前保存的约束、索引或统计对象的定义字符串与新建立的列数据类型进行匹配，并将结果命令的解析树排队以执行。
 *
 * 如果你有一个 WHERE 子句使用了不适用于新列类型的操作符，这可能会失败。
 */
static void fc_ATPostAlterTypeParse(Oid fc_oldId, Oid fc_oldRelId, Oid fc_refRelId, char *fc_cmd,
					 List **fc_wqueue, LOCKMODE fc_lockmode, bool fc_rewrite)
{
	List	   *fc_raw_parsetree_list;
	List	   *fc_querytree_list;
	ListCell   *fc_list_item;
	Relation	fc_rel;

	/*
	 * 我们期望只会得到 ALTER TABLE 和 CREATE INDEX 语句。因此，不需要通过 parse_analyze_*() 或重写器来处理它们，而是需要通过 parse_utilcmd.c 将它们准备好以供执行。
	 */
	fc_raw_parsetree_list = raw_parser(fc_cmd, RAW_PARSE_DEFAULT);
	fc_querytree_list = NIL;
	foreach(fc_list_item, fc_raw_parsetree_list)
	{
		RawStmt    *fc_rs = lfirst_node(RawStmt, fc_list_item);
		Node	   *fc_stmt = fc_rs->stmt;

		if (IsA(fc_stmt, IndexStmt))
			fc_querytree_list = lappend(fc_querytree_list,
									 transformIndexStmt(fc_oldRelId,
														(IndexStmt *) fc_stmt,
														fc_cmd));
		else if (IsA(fc_stmt, AlterTableStmt))
		{
			List	   *fc_beforeStmts;
			List	   *fc_afterStmts;

			fc_stmt = (Node *) transformAlterTableStmt(fc_oldRelId,
													(AlterTableStmt *) fc_stmt,
													fc_cmd,
													&fc_beforeStmts,
													&fc_afterStmts);
			fc_querytree_list = list_concat(fc_querytree_list, fc_beforeStmts);
			fc_querytree_list = lappend(fc_querytree_list, fc_stmt);
			fc_querytree_list = list_concat(fc_querytree_list, fc_afterStmts);
		}
		else if (IsA(fc_stmt, CreateStatsStmt))
			fc_querytree_list = lappend(fc_querytree_list,
									 transformStatsStmt(fc_oldRelId,
														(CreateStatsStmt *) fc_stmt,
														fc_cmd));
		else
			fc_querytree_list = lappend(fc_querytree_list, fc_stmt);
	}

	/* 调用者应该已经获取了我们需要的任何锁。 */
	fc_rel = relation_open(fc_oldRelId, NoLock);

	/*
	 * 将每个生成的命令附加到工作队列中的适当位置。
	 * 注意，这可能会导致创建全新的工作队列条目。
	 *
	 * 还要注意，我们必须调整命令的子类型，因为索引和约束的重新创建必须与初始创建有所不同。
	 */
	foreach(fc_list_item, fc_querytree_list)
	{
		Node	   *fc_stm = (Node *) lfirst(fc_list_item);
		AlteredTableInfo *fc_tab;

		fc_tab = fc_ATGetQueueEntry(fc_wqueue, fc_rel);

		if (IsA(fc_stm, IndexStmt))
		{
			IndexStmt  *fc_stmt = (IndexStmt *) fc_stm;
			AlterTableCmd *fc_newcmd;

			if (!fc_rewrite)
				fc_TryReuseIndex(fc_oldId, fc_stmt);
			fc_stmt->reset_default_tblspc = true;
			/* 保留索引的注释 */
			fc_stmt->idxcomment = GetComment(fc_oldId, RelationRelationId, 0);

			fc_newcmd = makeNode(AlterTableCmd);
			fc_newcmd->subtype = AT_ReAddIndex;
			fc_newcmd->def = (Node *) fc_stmt;
			fc_tab->subcmds[AT_PASS_OLD_INDEX] =
				lappend(fc_tab->subcmds[AT_PASS_OLD_INDEX], fc_newcmd);
		}
		else if (IsA(fc_stm, AlterTableStmt))
		{
			AlterTableStmt *fc_stmt = (AlterTableStmt *) fc_stm;
			ListCell   *fc_lcmd;

			foreach(fc_lcmd, fc_stmt->cmds)
			{
				AlterTableCmd *fc_cmd = lfirst_node(AlterTableCmd, fc_lcmd);

				if (fc_cmd->subtype == AT_AddIndex)
				{
					IndexStmt  *fc_indstmt;
					Oid			fc_indoid;

					fc_indstmt = castNode(IndexStmt, fc_cmd->def);
					fc_indoid = get_constraint_index(fc_oldId);

					if (!fc_rewrite)
						fc_TryReuseIndex(fc_indoid, fc_indstmt);
					/* 保留索引上的任何注释 */
					fc_indstmt->idxcomment = GetComment(fc_indoid,
													 RelationRelationId, 0);
					fc_indstmt->reset_default_tblspc = true;

					fc_cmd->subtype = AT_ReAddIndex;
					fc_tab->subcmds[AT_PASS_OLD_INDEX] =
						lappend(fc_tab->subcmds[AT_PASS_OLD_INDEX], fc_cmd);

					/* 重新创建约束上的任何注释 */
					fc_RebuildConstraintComment(fc_tab,
											 AT_PASS_OLD_INDEX,
											 fc_oldId,
											 fc_rel,
											 NIL,
											 fc_indstmt->idxname);
				}
				else if (fc_cmd->subtype == AT_AddConstraint)
				{
					Constraint *fc_con = castNode(Constraint, fc_cmd->def);

					fc_con->old_pktable_oid = fc_refRelId;
					/* 不重写外键的任一侧 */
					if (fc_con->contype == CONSTR_FOREIGN &&
						!fc_rewrite && fc_tab->rewrite == 0)
						fc_TryReuseForeignKey(fc_oldId, fc_con);
					fc_con->reset_default_tblspc = true;
					fc_cmd->subtype = AT_ReAddConstraint;
					fc_tab->subcmds[AT_PASS_OLD_CONSTR] =
						lappend(fc_tab->subcmds[AT_PASS_OLD_CONSTR], fc_cmd);

					/* 重新创建约束上的任何注释 */
					fc_RebuildConstraintComment(fc_tab,
											 AT_PASS_OLD_CONSTR,
											 fc_oldId,
											 fc_rel,
											 NIL,
											 fc_con->conname);
				}
				else if (fc_cmd->subtype == AT_SetNotNull)
				{
					/*
					 * 解析器将为主键索引/约束的列创建 AT_SetNotNull 子命令，
					 * 但我们在这里不需要对它们做任何处理，因为列的
					 * NOT NULL 标记已经传播到新的表定义中。
					 */
				}
				else
					elog(ERROR, "unexpected statement subtype: %d",
						 (int) fc_cmd->subtype);
			}
		}
		else if (IsA(fc_stm, AlterDomainStmt))
		{
			AlterDomainStmt *fc_stmt = (AlterDomainStmt *) fc_stm;

			if (fc_stmt->subtype == 'C')	/* ADD CONSTRAINT */
			{
				Constraint *fc_con = castNode(Constraint, fc_stmt->def);
				AlterTableCmd *fc_cmd = makeNode(AlterTableCmd);

				fc_cmd->subtype = AT_ReAddDomainConstraint;
				fc_cmd->def = (Node *) fc_stmt;
				fc_tab->subcmds[AT_PASS_OLD_CONSTR] =
					lappend(fc_tab->subcmds[AT_PASS_OLD_CONSTR], fc_cmd);

				/* 重新创建约束上的任何注释 */
				fc_RebuildConstraintComment(fc_tab,
										 AT_PASS_OLD_CONSTR,
										 fc_oldId,
										 NULL,
										 fc_stmt->typeName,
										 fc_con->conname);
			}
			else
				elog(ERROR, "unexpected statement subtype: %d",
					 (int) fc_stmt->subtype);
		}
		else if (IsA(fc_stm, CreateStatsStmt))
		{
			CreateStatsStmt *fc_stmt = (CreateStatsStmt *) fc_stm;
			AlterTableCmd *fc_newcmd;

			/* 保留统计对象的注释 */
			fc_stmt->stxcomment = GetComment(fc_oldId, StatisticExtRelationId, 0);

			fc_newcmd = makeNode(AlterTableCmd);
			fc_newcmd->subtype = AT_ReAddStatistics;
			fc_newcmd->def = (Node *) fc_stmt;
			fc_tab->subcmds[AT_PASS_MISC] =
				lappend(fc_tab->subcmds[AT_PASS_MISC], fc_newcmd);
		}
		else
			elog(ERROR, "unexpected statement type: %d",
				 (int) nodeTag(fc_stm));
	}

	relation_close(fc_rel, NoLock);
}

/*
 * ATPostAlterTypeParse() 的子程序，用于重新创建正在重建的表或域约束的任何现有注释。
 *
 * objid 是约束的 OID。
 * 对于表约束，传入 "rel"；对于域约束，传入 "domname"（域的合格名称作为字符串列表）。
 * （我们可以从 pg_constraint 条目中提取这些信息，以及 conname；但调用者已经有了它们，所以传递它们更方便。）
 */
static void fc_RebuildConstraintComment(AlteredTableInfo *fc_tab, int fc_pass, Oid fc_objid,
						 Relation fc_rel, List *fc_domname,
						 const char *fc_conname)
{
	CommentStmt *fc_cmd;
	char	   *fc_comment_str;
	AlterTableCmd *fc_newcmd;

	/* 查找所需对象的注释，如果没有则离开 */
	fc_comment_str = GetComment(fc_objid, ConstraintRelationId, 0);
	if (fc_comment_str == NULL)
		return;

	/* 构建 CommentStmt 节点，复制所有输入数据以确保安全 */
	fc_cmd = makeNode(CommentStmt);
	if (fc_rel)
	{
		fc_cmd->objtype = OBJECT_TABCONSTRAINT;
		fc_cmd->object = (Node *)
			list_make3(makeString(get_namespace_name(RelationGetNamespace(fc_rel))),
					   makeString(pstrdup(RelationGetRelationName(fc_rel))),
					   makeString(pstrdup(fc_conname)));
	}
	else
	{
		fc_cmd->objtype = OBJECT_DOMCONSTRAINT;
		fc_cmd->object = (Node *)
			list_make2(makeTypeNameFromNameList(copyObject(fc_domname)),
					   makeString(pstrdup(fc_conname)));
	}
	fc_cmd->comment = fc_comment_str;

	/* 将其附加到命令列表中 */
	fc_newcmd = makeNode(AlterTableCmd);
	fc_newcmd->subtype = AT_ReAddComment;
	fc_newcmd->def = (Node *) fc_cmd;
	fc_tab->subcmds[fc_pass] = lappend(fc_tab->subcmds[fc_pass], fc_newcmd);
}

/*
 * ATPostAlterTypeParse() 的子程序。调用 CheckIndexCompatible() 进行实际分析，
 * 然后根据该结论修改 IndexStmt。
 */
static void fc_TryReuseIndex(Oid fc_oldId, IndexStmt *fc_stmt)
{
	if (CheckIndexCompatible(fc_oldId,
							 fc_stmt->accessMethod,
							 fc_stmt->indexParams,
							 fc_stmt->excludeOpNames))
	{
		Relation	fc_irel = index_open(fc_oldId, NoLock);

		/* 如果是分区索引，则没有存储可共享。 */
		if (fc_irel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
		{
			fc_stmt->oldNode = fc_irel->rd_node.relNode;
			fc_stmt->oldCreateSubid = fc_irel->rd_createSubid;
			fc_stmt->oldFirstRelfilenodeSubid = fc_irel->rd_firstRelfilenodeSubid;
		}
		index_close(fc_irel, NoLock);
	}
}

/*
 * 用于 ATPostAlterTypeParse() 的子例程。
 *
 * 将旧的 P-F 等价运算符存入约束节点，以便 ATAddForeignKeyConstraint() 
 * 在确定是否可以跳过此约束的重验证时使用。
 */
static void fc_TryReuseForeignKey(Oid fc_oldId, Constraint *fc_con)
{
	HeapTuple	fc_tup;
	Datum		fc_adatum;
	bool		fc_isNull;
	ArrayType  *fc_arr;
	Oid		   *fc_rawarr;
	int			fc_numkeys;
	int			fc_i;

	Assert(fc_con->contype == CONSTR_FOREIGN);
	Assert(fc_con->old_conpfeqop == NIL);	/* 已经准备好该节点 */

	fc_tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fc_oldId));
	if (!HeapTupleIsValid(fc_tup)) /* 不应该发生 */
		elog(ERROR, "cache lookup failed for constraint %u", fc_oldId);

	fc_adatum = SysCacheGetAttr(CONSTROID, fc_tup,
							 Anum_pg_constraint_conpfeqop, &fc_isNull);
	if (fc_isNull)
		elog(ERROR, "null conpfeqop for constraint %u", fc_oldId);
	fc_arr = DatumGetArrayTypeP(fc_adatum);	/* 确保未被压缩 */
	fc_numkeys = ARR_DIMS(fc_arr)[0];
	/* 测试与 ri_FetchConstraintInfo() 中的测试相同 */
	if (ARR_NDIM(fc_arr) != 1 ||
		ARR_HASNULL(fc_arr) ||
		ARR_ELEMTYPE(fc_arr) != OIDOID)
		elog(ERROR, "conpfeqop is not a 1-D Oid array");
	fc_rawarr = (Oid *) ARR_DATA_PTR(fc_arr);

	/* 在我们的约束节点中存储运算符 Oid 的列表 */
	for (fc_i = 0; fc_i < fc_numkeys; fc_i++)
		fc_con->old_conpfeqop = lappend_oid(fc_con->old_conpfeqop, fc_rawarr[fc_i]);

	ReleaseSysCache(fc_tup);
}

/*
 * ALTER COLUMN .. OPTIONS ( ... )
 *
 * 返回已修改列的地址
 */
static ObjectAddress fc_ATExecAlterColumnGenericOptions(Relation fc_rel,
								const char *fc_colName,
								List *fc_options,
								LOCKMODE fc_lockmode)
{
	Relation	fc_ftrel;
	Relation	fc_attrel;
	ForeignServer *fc_server;
	ForeignDataWrapper *fc_fdw;
	HeapTuple	fc_tuple;
	HeapTuple	fc_newtuple;
	bool		fc_isnull;
	Datum		fc_repl_val[Natts_pg_attribute];
	bool		fc_repl_null[Natts_pg_attribute];
	bool		fc_repl_repl[Natts_pg_attribute];
	Datum		fc_datum;
	Form_pg_foreign_table fc_fttableform;
	Form_pg_attribute fc_atttableform;
	AttrNumber	fc_attnum;
	ObjectAddress fc_address;

	if (fc_options == NIL)
		return InvalidObjectAddress;

	/* 首先，确定与外部表关联的 FDW 验证器。 */
	fc_ftrel = table_open(ForeignTableRelationId, AccessShareLock);
	fc_tuple = SearchSysCache1(FOREIGNTABLEREL, fc_rel->rd_id);
	if (!HeapTupleIsValid(fc_tuple))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("foreign table \"%s\" does not exist",
						RelationGetRelationName(fc_rel))));
	fc_fttableform = (Form_pg_foreign_table) GETSTRUCT(fc_tuple);
	fc_server = GetForeignServer(fc_fttableform->ftserver);
	fc_fdw = GetForeignDataWrapper(fc_server->fdwid);

	table_close(fc_ftrel, AccessShareLock);
	ReleaseSysCache(fc_tuple);

	fc_attrel = table_open(AttributeRelationId, RowExclusiveLock);
	fc_tuple = SearchSysCacheAttName(RelationGetRelid(fc_rel), fc_colName);
	if (!HeapTupleIsValid(fc_tuple))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_COLUMN),
				 errmsg("column \"%s\" of relation \"%s\" does not exist",
						fc_colName, RelationGetRelationName(fc_rel))));

	/* 防止他们修改系统属性 */
	fc_atttableform = (Form_pg_attribute) GETSTRUCT(fc_tuple);
	fc_attnum = fc_atttableform->attnum;
	if (fc_attnum <= 0)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot alter system column \"%s\"", fc_colName)));


	/* 初始化新元组值的缓冲区 */
	memset(fc_repl_val, 0, sizeof(fc_repl_val));
	memset(fc_repl_null, false, sizeof(fc_repl_null));
	memset(fc_repl_repl, false, sizeof(fc_repl_repl));

	/* 提取当前选项 */
	fc_datum = SysCacheGetAttr(ATTNAME,
							fc_tuple,
							Anum_pg_attribute_attfdwoptions,
							&fc_isnull);
	if (fc_isnull)
		fc_datum = PointerGetDatum(NULL);

	/* 转换选项 */
	fc_datum = transformGenericOptions(AttributeRelationId,
									fc_datum,
									fc_options,
									fc_fdw->fdwvalidator);

	if (PointerIsValid(DatumGetPointer(fc_datum)))
		fc_repl_val[Anum_pg_attribute_attfdwoptions - 1] = fc_datum;
	else
		fc_repl_null[Anum_pg_attribute_attfdwoptions - 1] = true;

	fc_repl_repl[Anum_pg_attribute_attfdwoptions - 1] = true;

	/* 一切看起来都很好 - 更新元组 */

	fc_newtuple = heap_modify_tuple(fc_tuple, RelationGetDescr(fc_attrel),
								 fc_repl_val, fc_repl_null, fc_repl_repl);

	CatalogTupleUpdate(fc_attrel, &fc_newtuple->t_self, fc_newtuple);

	InvokeObjectPostAlterHook(RelationRelationId,
							  RelationGetRelid(fc_rel),
							  fc_atttableform->attnum);
	ObjectAddressSubSet(fc_address, RelationRelationId,
						RelationGetRelid(fc_rel), fc_attnum);

	ReleaseSysCache(fc_tuple);

	table_close(fc_attrel, RowExclusiveLock);

	heap_freetuple(fc_newtuple);

	return fc_address;
}

/*
 * ALTER TABLE OWNER
 *
 * 如果我们从表递归到其索引、序列或压缩表，则 recursing 为 true。
 * 我们不允许单独更改这些事物的所有权。 
 * 此外，我们可以跳过权限检查（这不仅仅是一种优化，
 * 否则我们将无法正确处理压缩表）。
 *
 * 如果 ALTER TYPE OWNER 正在调用我们修复一个独立的复合类型，
 * recursing 也是 true。
 */
void ATExecChangeOwner(Oid fc_relationOid, Oid fc_newOwnerId, bool fc_recursing, LOCKMODE fc_lockmode)
{
	Relation	fc_target_rel;
	Relation	fc_class_rel;
	HeapTuple	fc_tuple;
	Form_pg_class fc_tuple_class;

	/*
	 * 在目标表上获取独占锁，直到事务结束。使用
	 * relation_open 以便我们可以处理索引和序列。
	 */
	fc_target_rel = relation_open(fc_relationOid, fc_lockmode);

	/* 也获取其 pg_class 元组 */
	fc_class_rel = table_open(RelationRelationId, RowExclusiveLock);

	fc_tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(fc_relationOid));
	if (!HeapTupleIsValid(fc_tuple))
		elog(ERROR, "cache lookup failed for relation %u", fc_relationOid);
	fc_tuple_class = (Form_pg_class) GETSTRUCT(fc_tuple);

	/* 我们能更改该元组的所有权吗？ */
	switch (fc_tuple_class->relkind)
	{
		case RELKIND_RELATION:
		case RELKIND_VIEW:
		case RELKIND_MATVIEW:
		case RELKIND_FOREIGN_TABLE:
		case RELKIND_PARTITIONED_TABLE:
			/* 可以更改所有者 */
			break;
		case RELKIND_INDEX:
			if (!fc_recursing)
			{
				/*
				 * 因为 ALTER INDEX OWNER 曾经是被允许的，
				 * 实际上是由旧版本的 pg_dump 生成的，
				 * 我们发出警告而不执行任何操作，而不是出现错误。
				 * 此外，为了在恢复这些旧转储时避免不必要的干扰，
				 * 如果命令本身是无操作，则完全不发言。
				 */
				if (fc_tuple_class->relowner != fc_newOwnerId)
					ereport(WARNING,
							(errcode(ERRCODE_WRONG_OBJECT_TYPE),
							 errmsg("cannot change owner of index \"%s\"",
									NameStr(fc_tuple_class->relname)),
							 errhint("Change the ownership of the index's table, instead.")));
				/* 快速修补以通过无操作路径退出 */
				fc_newOwnerId = fc_tuple_class->relowner;
			}
			break;
		case RELKIND_PARTITIONED_INDEX:
			if (fc_recursing)
				break;
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("cannot change owner of index \"%s\"",
							NameStr(fc_tuple_class->relname)),
					 errhint("Change the ownership of the index's table, instead.")));
			break;
		case RELKIND_SEQUENCE:
			if (!fc_recursing &&
				fc_tuple_class->relowner != fc_newOwnerId)
			{
				/* 如果它是一个拥有的序列，禁止单独更改它 */
				Oid			fc_tableId;
				int32		fc_colId;

				if (sequenceIsOwned(fc_relationOid, DEPENDENCY_AUTO, &fc_tableId, &fc_colId) ||
					sequenceIsOwned(fc_relationOid, DEPENDENCY_INTERNAL, &fc_tableId, &fc_colId))
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("cannot change owner of sequence \"%s\"",
									NameStr(fc_tuple_class->relname)),
							 errdetail("Sequence \"%s\" is linked to table \"%s\".",
									   NameStr(fc_tuple_class->relname),
									   get_rel_name(fc_tableId))));
			}
			break;
		case RELKIND_COMPOSITE_TYPE:
			if (fc_recursing)
				break;
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("\"%s\" is a composite type",
							NameStr(fc_tuple_class->relname)),
					 errhint("Use ALTER TYPE instead.")));
			break;
		case RELKIND_TOASTVALUE:
			if (fc_recursing)
				break;
			/* FALL THRU */
		default:
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("cannot change owner of relation \"%s\"",
							NameStr(fc_tuple_class->relname)),
					 errdetail_relkind_not_supported(fc_tuple_class->relkind)));
	}

	/*
	 * 如果新所有者与现有所有者相同，则视为命令已成功。
	 * 这是为了转储恢复的目的。
	 */
	if (fc_tuple_class->relowner != fc_newOwnerId)
	{
		Datum		fc_repl_val[Natts_pg_class];
		bool		fc_repl_null[Natts_pg_class];
		bool		fc_repl_repl[Natts_pg_class];
		Acl		   *fc_newAcl;
		Datum		fc_aclDatum;
		bool		fc_isNull;
		HeapTuple	fc_newtuple;

		/* 在递归到索引或压缩表时跳过权限检查 */
		if (!fc_recursing)
		{
			/* 超级用户可以始终执行此操作 */
			if (!superuser())
			{
				Oid			fc_namespaceOid = fc_tuple_class->relnamespace;
				AclResult	fc_aclresult;

				/* 否则，必须是现有对象的所有者 */
				if (!pg_class_ownercheck(fc_relationOid, GetUserId()))
					aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(fc_relationOid)),
								   RelationGetRelationName(fc_target_rel));

				/* 必须能够成为新所有者 */
				check_is_member_of_role(GetUserId(), fc_newOwnerId);

				/* 新所有者必须对命名空间具有创建权限 */
				fc_aclresult = pg_namespace_aclcheck(fc_namespaceOid, fc_newOwnerId,
												  ACL_CREATE);
				if (fc_aclresult != ACLCHECK_OK)
					aclcheck_error(fc_aclresult, OBJECT_SCHEMA,
								   get_namespace_name(fc_namespaceOid));
			}
		}

		memset(fc_repl_null, false, sizeof(fc_repl_null));
		memset(fc_repl_repl, false, sizeof(fc_repl_repl));

		fc_repl_repl[Anum_pg_class_relowner - 1] = true;
		fc_repl_val[Anum_pg_class_relowner - 1] = ObjectIdGetDatum(fc_newOwnerId);

		/*
		 * 确定新所有者的修改后 ACL。这仅在 ACL 非空时必要。
		 */
		fc_aclDatum = SysCacheGetAttr(RELOID, fc_tuple,
								   Anum_pg_class_relacl,
								   &fc_isNull);
		if (!fc_isNull)
		{
			fc_newAcl = aclnewowner(DatumGetAclP(fc_aclDatum),
								 fc_tuple_class->relowner, fc_newOwnerId);
			fc_repl_repl[Anum_pg_class_relacl - 1] = true;
			fc_repl_val[Anum_pg_class_relacl - 1] = PointerGetDatum(fc_newAcl);
		}

		fc_newtuple = heap_modify_tuple(fc_tuple, RelationGetDescr(fc_class_rel), fc_repl_val, fc_repl_null, fc_repl_repl);

		CatalogTupleUpdate(fc_class_rel, &fc_newtuple->t_self, fc_newtuple);

		heap_freetuple(fc_newtuple);

		/*
		 * 我们必须同样更新每列的 ACL，以反映新所有者；
		 * 出于整洁的原因，这分开作为一个子例程。
		 */
		fc_change_owner_fix_column_acls(fc_relationOid,
									 fc_tuple_class->relowner,
									 fc_newOwnerId);

		/*
		 * 更新所有者依赖引用（如果有的话）。复合类型没有，因为它是为了pg_type条目而被跟踪，而不是在这里；
		 * 索引和TOAST表也没有自己的条目。
		 */
		if (fc_tuple_class->relkind != RELKIND_COMPOSITE_TYPE &&
			fc_tuple_class->relkind != RELKIND_INDEX &&
			fc_tuple_class->relkind != RELKIND_PARTITIONED_INDEX &&
			fc_tuple_class->relkind != RELKIND_TOASTVALUE)
			changeDependencyOnOwner(RelationRelationId, fc_relationOid,
									fc_newOwnerId);

		/*
		 * 如果有行类型，还要更改表的行类型的所有权
		 */
		if (OidIsValid(fc_tuple_class->reltype))
			AlterTypeOwnerInternal(fc_tuple_class->reltype, fc_newOwnerId);

		/*
		 * 如果我们在操作一个表或物化视图，还要更改属于该关系的任何索引和序列的所有权，以及它的toast表（如果它有的话）。
		 */
		if (fc_tuple_class->relkind == RELKIND_RELATION ||
			fc_tuple_class->relkind == RELKIND_PARTITIONED_TABLE ||
			fc_tuple_class->relkind == RELKIND_MATVIEW ||
			fc_tuple_class->relkind == RELKIND_TOASTVALUE)
		{
			List	   *fc_index_oid_list;
			ListCell   *fc_i;

			/* 查找属于此关系的所有索引 */
			fc_index_oid_list = RelationGetIndexList(fc_target_rel);

			/* 对于每个索引，递归更改其所有权 */
			foreach(fc_i, fc_index_oid_list)
				ATExecChangeOwner(lfirst_oid(fc_i), fc_newOwnerId, true, fc_lockmode);

			list_free(fc_index_oid_list);
		}

		/* 如果有toast表，则递归更改其所有权 */
		if (fc_tuple_class->reltoastrelid != InvalidOid)
			ATExecChangeOwner(fc_tuple_class->reltoastrelid, fc_newOwnerId,
							  true, fc_lockmode);

		/* 如果有依赖序列，则递归更改它们 */
		fc_change_owner_recurse_to_sequences(fc_relationOid, fc_newOwnerId, fc_lockmode);
	}

	InvokeObjectPostAlterHook(RelationRelationId, fc_relationOid, 0);

	ReleaseSysCache(fc_tuple);
	table_close(fc_class_rel, RowExclusiveLock);
	relation_close(fc_target_rel, NoLock);
}

/*
 * change_owner_fix_column_acls
 *
 * ATExecChangeOwner的帮助函数。扫描表的列并修复任何非空的列ACL以反映新所有者。
 */
static void fc_change_owner_fix_column_acls(Oid fc_relationOid, Oid fc_oldOwnerId, Oid fc_newOwnerId)
{
	Relation	fc_attRelation;
	SysScanDesc fc_scan;
	ScanKeyData fc_key[1];
	HeapTuple	fc_attributeTuple;

	fc_attRelation = table_open(AttributeRelationId, RowExclusiveLock);
	ScanKeyInit(&fc_key[0],
				Anum_pg_attribute_attrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_relationOid));
	fc_scan = systable_beginscan(fc_attRelation, AttributeRelidNumIndexId,
							  true, NULL, 1, fc_key);
	while (HeapTupleIsValid(fc_attributeTuple = systable_getnext(fc_scan)))
	{
		Form_pg_attribute fc_att = (Form_pg_attribute) GETSTRUCT(fc_attributeTuple);
		Datum		fc_repl_val[Natts_pg_attribute];
		bool		fc_repl_null[Natts_pg_attribute];
		bool		fc_repl_repl[Natts_pg_attribute];
		Acl		   *fc_newAcl;
		Datum		fc_aclDatum;
		bool		fc_isNull;
		HeapTuple	fc_newtuple;

		/* 忽略已删除的列 */
		if (fc_att->attisdropped)
			continue;

		fc_aclDatum = heap_getattr(fc_attributeTuple,
								Anum_pg_attribute_attacl,
								RelationGetDescr(fc_attRelation),
								&fc_isNull);
		/* 空ACL不需要更改 */
		if (fc_isNull)
			continue;

		memset(fc_repl_null, false, sizeof(fc_repl_null));
		memset(fc_repl_repl, false, sizeof(fc_repl_repl));

		fc_newAcl = aclnewowner(DatumGetAclP(fc_aclDatum),
							 fc_oldOwnerId, fc_newOwnerId);
		fc_repl_repl[Anum_pg_attribute_attacl - 1] = true;
		fc_repl_val[Anum_pg_attribute_attacl - 1] = PointerGetDatum(fc_newAcl);

		fc_newtuple = heap_modify_tuple(fc_attributeTuple,
									 RelationGetDescr(fc_attRelation),
									 fc_repl_val, fc_repl_null, fc_repl_repl);

		CatalogTupleUpdate(fc_attRelation, &fc_newtuple->t_self, fc_newtuple);

		heap_freetuple(fc_newtuple);
	}
	systable_endscan(fc_scan);
	table_close(fc_attRelation, RowExclusiveLock);
}

/*
 * change_owner_recurse_to_sequences
 *
 * ATExecChangeOwner的帮助函数。检查pg_depend，寻找依赖于序列列的序列，并更改它们的所有权。
 */
static void fc_change_owner_recurse_to_sequences(Oid fc_relationOid, Oid fc_newOwnerId, LOCKMODE fc_lockmode)
{
	Relation	fc_depRel;
	SysScanDesc fc_scan;
	ScanKeyData fc_key[2];
	HeapTuple	fc_tup;

	/*
	 * SERIAL序列是那些对表的某一列具有自动依赖关系的序列（我们不关心*哪一*列，确切来说）。
	 */
	fc_depRel = table_open(DependRelationId, AccessShareLock);

	ScanKeyInit(&fc_key[0],
				Anum_pg_depend_refclassid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(RelationRelationId));
	ScanKeyInit(&fc_key[1],
				Anum_pg_depend_refobjid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_relationOid));
	/* 我们不指定refobjsubid */

	fc_scan = systable_beginscan(fc_depRel, DependReferenceIndexId, true,
							  NULL, 2, fc_key);

	while (HeapTupleIsValid(fc_tup = systable_getnext(fc_scan)))
	{
		Form_pg_depend fc_depForm = (Form_pg_depend) GETSTRUCT(fc_tup);
		Relation	fc_seqRel;

		/* 跳过除对列的自动依赖关系之外的依赖关系 */
		if (fc_depForm->refobjsubid == 0 ||
			fc_depForm->classid != RelationRelationId ||
			fc_depForm->objsubid != 0 ||
			!(fc_depForm->deptype == DEPENDENCY_AUTO || fc_depForm->deptype == DEPENDENCY_INTERNAL))
			continue;

		/* 如果是索引，则使用relation_open以防万一 */
		fc_seqRel = relation_open(fc_depForm->objid, fc_lockmode);

		/* 跳过非序列关系 */
		if (RelationGetForm(fc_seqRel)->relkind != RELKIND_SEQUENCE)
		{
			/* 无需保持锁定 */
			relation_close(fc_seqRel, fc_lockmode);
			continue;
		}

		/* 我们在更改序列时不需要关闭它。 */
		ATExecChangeOwner(fc_depForm->objid, fc_newOwnerId, true, fc_lockmode);

		/* 现在我们可以关闭它。直到事务结束保持锁定。 */
		relation_close(fc_seqRel, NoLock);
	}

	systable_endscan(fc_scan);

	relation_close(fc_depRel, AccessShareLock);
}

/*
 * ALTER TABLE CLUSTER ON
 *
 * 我们唯一需要做的就是改变不聚类位。
 *
 * 返回新聚类索引的地址。
 */
static ObjectAddress fc_ATExecClusterOn(Relation fc_rel, const char *fc_indexName, LOCKMODE fc_lockmode)
{
	Oid			fc_indexOid;
	ObjectAddress fc_address;

	fc_indexOid = get_relname_relid(fc_indexName, fc_rel->rd_rel->relnamespace);

	if (!OidIsValid(fc_indexOid))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("index \"%s\" for table \"%s\" does not exist",
						fc_indexName, RelationGetRelationName(fc_rel))));

	/* 检查索引是否有效以进行聚类 */
	check_index_is_clusterable(fc_rel, fc_indexOid, fc_lockmode);

	/* 并进行工作 */
	mark_index_clustered(fc_rel, fc_indexOid, false);

	ObjectAddressSet(fc_address,
					 RelationRelationId, fc_indexOid);

	return fc_address;
}

/*
 * ALTER TABLE SET WITHOUT CLUSTER
 *
 * 我们需要找到在表上设置了不聚类位的任何索引并将其关闭。
 */
static void fc_ATExecDropCluster(Relation fc_rel, LOCKMODE fc_lockmode)
{
	mark_index_clustered(fc_rel, InvalidOid, false);
}

/*
 * SET ACCESS METHOD的准备阶段
 *
 * 检查访问方法是否存在。如果它与表的当前访问方法相同，则无操作。否则，需要重写表。
 */
static void fc_ATPrepSetAccessMethod(AlteredTableInfo *fc_tab, Relation fc_rel, const char *fc_amname)
{
	Oid			fc_amoid;

	/* 检查表访问方法是否存在 */
	fc_amoid = get_table_am_oid(fc_amname, false);

	if (fc_rel->rd_rel->relam == fc_amoid)
		return;

	/* 保存信息以进行阶段3以进行实际工作 */
	fc_tab->rewrite |= AT_REWRITE_ACCESS_METHOD;
	fc_tab->newAccessMethod = fc_amoid;
}

/*
 * ALTER TABLE SET TABLESPACE
 */
static void fc_ATPrepSetTableSpace(AlteredTableInfo *fc_tab, Relation fc_rel, const char *fc_tablespacename, LOCKMODE fc_lockmode)
{
	Oid			fc_tablespaceId;

	/* 检查表空间是否存在 */
	fc_tablespaceId = get_tablespace_oid(fc_tablespacename, false);

	/* 检查权限，除非移动到数据库的默认值 */
	if (OidIsValid(fc_tablespaceId) && fc_tablespaceId != MyDatabaseTableSpace)
	{
		AclResult	fc_aclresult;

		fc_aclresult = pg_tablespace_aclcheck(fc_tablespaceId, GetUserId(), ACL_CREATE);
		if (fc_aclresult != ACLCHECK_OK)
			aclcheck_error(fc_aclresult, OBJECT_TABLESPACE, fc_tablespacename);
	}

	/* 保存信息以进行阶段3以进行实际工作 */
	if (OidIsValid(fc_tab->newTableSpace))
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("cannot have multiple SET TABLESPACE subcommands")));

	fc_tab->newTableSpace = fc_tablespaceId;
}

/*
 * 设置、重置或替换reloptions。
 */
static void fc_ATExecSetRelOptions(Relation fc_rel, List *fc_defList, AlterTableType fc_operation,
					LOCKMODE fc_lockmode)
{
	Oid			fc_relid;
	Relation	fc_pgclass;
	HeapTuple	fc_tuple;
	HeapTuple	fc_newtuple;
	Datum		fc_datum;
	bool		fc_isnull;
	Datum		fc_newOptions;
	Datum		fc_repl_val[Natts_pg_class];
	bool		fc_repl_null[Natts_pg_class];
	bool		fc_repl_repl[Natts_pg_class];
	static char *fc_validnsps[] = HEAP_RELOPT_NAMESPACES;

	if (fc_defList == NIL && fc_operation != AT_ReplaceRelOptions)
		return;					/* 无需操作 */

	fc_pgclass = table_open(RelationRelationId, RowExclusiveLock);

	/* 获取堆元组 */
	fc_relid = RelationGetRelid(fc_rel);
	fc_tuple = SearchSysCacheLocked1(RELOID, ObjectIdGetDatum(fc_relid));
	if (!HeapTupleIsValid(fc_tuple))
		elog(ERROR, "cache lookup failed for relation %u", fc_relid);

	if (fc_operation == AT_ReplaceRelOptions)
	{
		/*
		 * 如果我们应该替换reloptions列表，我们只是装作之前没有。
		 */
		fc_datum = (Datum) 0;
		fc_isnull = true;
	}
	else
	{
		/* 获取旧的reloptions */
		fc_datum = SysCacheGetAttr(RELOID, fc_tuple, Anum_pg_class_reloptions,
								&fc_isnull);
	}

	/* 生成新的提议reloptions（文本数组） */
	fc_newOptions = transformRelOptions(fc_isnull ? (Datum) 0 : fc_datum,
									 fc_defList, NULL, fc_validnsps, false,
									 fc_operation == AT_ResetRelOptions);

	/* 验证 */
	switch (fc_rel->rd_rel->relkind)
	{
		case RELKIND_RELATION:
		case RELKIND_TOASTVALUE:
		case RELKIND_MATVIEW:
			(void) heap_reloptions(fc_rel->rd_rel->relkind, fc_newOptions, true);
			break;
		case RELKIND_PARTITIONED_TABLE:
			(void) partitioned_table_reloptions(fc_newOptions, true);
			break;
		case RELKIND_VIEW:
			(void) view_reloptions(fc_newOptions, true);
			break;
		case RELKIND_INDEX:
		case RELKIND_PARTITIONED_INDEX:
			(void) index_reloptions(fc_rel->rd_indam->amoptions, fc_newOptions, true);
			break;
		default:
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("cannot set options for relation \"%s\"",
							RelationGetRelationName(fc_rel)),
					 errdetail_relkind_not_supported(fc_rel->rd_rel->relkind)));
			break;
	}

	/* 特殊情况验证视图选项 */
	if (fc_rel->rd_rel->relkind == RELKIND_VIEW)
	{
		Query	   *fc_view_query = get_view_query(fc_rel);
		List	   *fc_view_options = untransformRelOptions(fc_newOptions);
		ListCell   *fc_cell;
		bool		fc_check_option = false;

		foreach(fc_cell, fc_view_options)
		{
			DefElem    *fc_defel = (DefElem *) lfirst(fc_cell);

			if (strcmp(fc_defel->defname, "check_option") == 0)
				fc_check_option = true;
		}

		/*
		 * 如果指定了检查选项，查看视图是否
		 * 实际上可以自动更新。
		 */
		if (fc_check_option)
		{
			const char *fc_view_updatable_error =
			view_query_is_auto_updatable(fc_view_query, true);

			if (fc_view_updatable_error)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("WITH CHECK OPTION is supported only on automatically updatable views"),
						 errhint("%s", _(fc_view_updatable_error))));
		}
	}

	/*
	 * 我们只需要在这里更新pg_class行；新的选项将
	 * 在提交后缓存无效时传播到relcaches中。
	 */
	memset(fc_repl_val, 0, sizeof(fc_repl_val));
	memset(fc_repl_null, false, sizeof(fc_repl_null));
	memset(fc_repl_repl, false, sizeof(fc_repl_repl));

	if (fc_newOptions != (Datum) 0)
		fc_repl_val[Anum_pg_class_reloptions - 1] = fc_newOptions;
	else
		fc_repl_null[Anum_pg_class_reloptions - 1] = true;

	fc_repl_repl[Anum_pg_class_reloptions - 1] = true;

	fc_newtuple = heap_modify_tuple(fc_tuple, RelationGetDescr(fc_pgclass),
								 fc_repl_val, fc_repl_null, fc_repl_repl);

	CatalogTupleUpdate(fc_pgclass, &fc_newtuple->t_self, fc_newtuple);
	UnlockTuple(fc_pgclass, &fc_tuple->t_self, InplaceUpdateTupleLock);

	InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(fc_rel), 0);

	heap_freetuple(fc_newtuple);

	ReleaseSysCache(fc_tuple);

	/* 如果存在，重复整个操作以处理toast表 */
	if (OidIsValid(fc_rel->rd_rel->reltoastrelid))
	{
		Relation	fc_toastrel;
		Oid			fc_toastid = fc_rel->rd_rel->reltoastrelid;

		fc_toastrel = table_open(fc_toastid, fc_lockmode);

		/* 获取堆元组 */
		fc_tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(fc_toastid));
		if (!HeapTupleIsValid(fc_tuple))
			elog(ERROR, "cache lookup failed for relation %u", fc_toastid);

		if (fc_operation == AT_ReplaceRelOptions)
		{
			/*
			 * 如果我们需要替换reloptions列表，我们只需
			 * 假装之前没有任何选项。
			 */
			fc_datum = (Datum) 0;
			fc_isnull = true;
		}
		else
		{
			/* 获取旧的reloptions */
			fc_datum = SysCacheGetAttr(RELOID, fc_tuple, Anum_pg_class_reloptions,
									&fc_isnull);
		}

		fc_newOptions = transformRelOptions(fc_isnull ? (Datum) 0 : fc_datum,
										 fc_defList, "toast", fc_validnsps, false,
										 fc_operation == AT_ResetRelOptions);

		(void) heap_reloptions(RELKIND_TOASTVALUE, fc_newOptions, true);

		memset(fc_repl_val, 0, sizeof(fc_repl_val));
		memset(fc_repl_null, false, sizeof(fc_repl_null));
		memset(fc_repl_repl, false, sizeof(fc_repl_repl));

		if (fc_newOptions != (Datum) 0)
			fc_repl_val[Anum_pg_class_reloptions - 1] = fc_newOptions;
		else
			fc_repl_null[Anum_pg_class_reloptions - 1] = true;

		fc_repl_repl[Anum_pg_class_reloptions - 1] = true;

		fc_newtuple = heap_modify_tuple(fc_tuple, RelationGetDescr(fc_pgclass),
									 fc_repl_val, fc_repl_null, fc_repl_repl);

		CatalogTupleUpdate(fc_pgclass, &fc_newtuple->t_self, fc_newtuple);

		InvokeObjectPostAlterHookArg(RelationRelationId,
									 RelationGetRelid(fc_toastrel), 0,
									 InvalidOid, true);

		heap_freetuple(fc_newtuple);

		ReleaseSysCache(fc_tuple);

		table_close(fc_toastrel, NoLock);
	}

	table_close(fc_pgclass, RowExclusiveLock);
}

/*
 * 在没有元组重写的情况下执行ALTER TABLE SET TABLESPACE，
 * 因此我们只想尽快复制数据。
 */
static void fc_ATExecSetTableSpace(Oid fc_tableOid, Oid fc_newTableSpace, LOCKMODE fc_lockmode)
{
	Relation	fc_rel;
	Oid			fc_reltoastrelid;
	Oid			fc_newrelfilenode;
	RelFileNode fc_newrnode;
	List	   *fc_reltoastidxids = NIL;
	ListCell   *fc_lc;

	/*
	 * 在这里需要锁定，以防我们递归到toast表或索引
	 */
	fc_rel = relation_open(fc_tableOid, fc_lockmode);

	/* 首先检查关系是否可以移动到新表空间 */
	if (!CheckRelationTableSpaceMove(fc_rel, fc_newTableSpace))
	{
		InvokeObjectPostAlterHook(RelationRelationId,
								  RelationGetRelid(fc_rel), 0);
		relation_close(fc_rel, NoLock);
		return;
	}

	fc_reltoastrelid = fc_rel->rd_rel->reltoastrelid;
	/* 如果必要，获取toast关系上的索引列表 */
	if (OidIsValid(fc_reltoastrelid))
	{
		Relation	fc_toastRel = relation_open(fc_reltoastrelid, fc_lockmode);

		fc_reltoastidxids = RelationGetIndexList(fc_toastRel);
		relation_close(fc_toastRel, fc_lockmode);
	}

	/*
	 * relfilenodes在不同的表空间中不是唯一的，因此我们需要
	 * 在新表空间中分配一个新的relfilenode。
	 */
	fc_newrelfilenode = GetNewRelFileNode(fc_newTableSpace, NULL,
									   fc_rel->rd_rel->relpersistence);

	/* 打开旧关系和新关系 */
	fc_newrnode = fc_rel->rd_node;
	fc_newrnode.relNode = fc_newrelfilenode;
	fc_newrnode.spcNode = fc_newTableSpace;

	/* 将工作交给AM以实际创建新的文件节点并复制数据 */
	if (fc_rel->rd_rel->relkind == RELKIND_INDEX)
	{
		fc_index_copy_data(fc_rel, fc_newrnode);
	}
	else
	{
		Assert(RELKIND_HAS_TABLE_AM(fc_rel->rd_rel->relkind));
		table_relation_copy_data(fc_rel, &fc_newrnode);
	}

	/*
	 * 更新pg_class行。
	 *
	 * 注意：如果允许在pg_class或其索引上执行ATExecSetTableSpace()
	 * ，则这将无效（上述复制将不包含
	 * 更新后的pg_class条目），但使用
	 * CheckRelationTableSpaceMove()是禁止的。
	 */
	SetRelationTableSpace(fc_rel, fc_newTableSpace, fc_newrelfilenode);

	InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(fc_rel), 0);

	RelationAssumeNewRelfilenode(fc_rel);

	relation_close(fc_rel, NoLock);

	/* 确保reltablespace更改是可见的 */
	CommandCounterIncrement();

	/* 也移动相关的toast关系和/或索引 */
	if (OidIsValid(fc_reltoastrelid))
		fc_ATExecSetTableSpace(fc_reltoastrelid, fc_newTableSpace, fc_lockmode);
	foreach(fc_lc, fc_reltoastidxids)
		fc_ATExecSetTableSpace(lfirst_oid(fc_lc), fc_newTableSpace, fc_lockmode);

	/* 清理 */
	list_free(fc_reltoastidxids);
}

/*
 * 对于没有存储且需要保留表空间的关系，
 * ALTER TABLE SET TABLESPACE的特殊处理。
 *
 * 由于这些没有存储，因此可以通过简单的
 * 仅元数据操作来更新表空间。
 */
static void fc_ATExecSetTableSpaceNoStorage(Relation fc_rel, Oid fc_newTableSpace)
{
	/*
	 * 不应该在有存储的关系上调用；这些将在
	 * 第3阶段处理。
	 */
	Assert(!RELKIND_HAS_STORAGE(fc_rel->rd_rel->relkind));

	/* 检查关系是否可以移动到其新的表空间 */
	if (!CheckRelationTableSpaceMove(fc_rel, fc_newTableSpace))
	{
		InvokeObjectPostAlterHook(RelationRelationId,
								  RelationGetRelid(fc_rel),
								  0);
		return;
	}

	/* 更新可以进行，因此更改reltablespace */
	SetRelationTableSpace(fc_rel, fc_newTableSpace, InvalidOid);

	InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(fc_rel), 0);

	/* 确保reltablespace更改是可见的 */
	CommandCounterIncrement();
}

/*
 * ALTER TABLE ALL ... SET TABLESPACE
 *
 * 允许用户将当前数据库中给定表空间中某类所有对象
 * 移动到另一个表空间。对象也可以基于对象的
 * 所有者进行选择，以便允许用户仅移动他们的对象。
 * 用户必须对新表空间拥有CREATE权限，和以往一样。主要
 * 的权限处理由底层表移动功能完成。
 *
 * 所有待移动的对象首先被锁定。如果指定了NOWAIT而无法
 * 获得锁，则我们报告错误(ERROR)。
 */
Oid AlterTableMoveAll(AlterTableMoveAllStmt *fc_stmt)
{
	List	   *fc_relations = NIL;
	ListCell   *fc_l;
	ScanKeyData fc_key[1];
	Relation	fc_rel;
	TableScanDesc fc_scan;
	HeapTuple	fc_tuple;
	Oid			fc_orig_tablespaceoid;
	Oid			fc_new_tablespaceoid;
	List	   *fc_role_oids = roleSpecsToIds(fc_stmt->roles);

	/* 确保我们没有被要求移动无法移动的对象 */
	if (fc_stmt->objtype != OBJECT_TABLE && fc_stmt->objtype != OBJECT_INDEX &&
		fc_stmt->objtype != OBJECT_MATVIEW)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("only tables, indexes, and materialized views exist in tablespaces")));

	/* 获取原始和新表空间OID */
	fc_orig_tablespaceoid = get_tablespace_oid(fc_stmt->orig_tablespacename, false);
	fc_new_tablespaceoid = get_tablespace_oid(fc_stmt->new_tablespacename, false);

	/* 不能在pg_global中移动共享关系 */
	/* 这也被 ATExecSetTableSpace 检查，但最好早点停止 */
	if (fc_orig_tablespaceoid == GLOBALTABLESPACE_OID ||
		fc_new_tablespaceoid == GLOBALTABLESPACE_OID)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("cannot move relations in to or out of pg_global tablespace")));

	/*
	 * 必须拥有新表空间的 CREATE 权限，除非它是
	 * 数据库的默认表空间（所有用户默认都有 CREATE
	 * 权限）。
	 */
	if (OidIsValid(fc_new_tablespaceoid) && fc_new_tablespaceoid != MyDatabaseTableSpace)
	{
		AclResult	fc_aclresult;

		fc_aclresult = pg_tablespace_aclcheck(fc_new_tablespaceoid, GetUserId(),
										   ACL_CREATE);
		if (fc_aclresult != ACLCHECK_OK)
			aclcheck_error(fc_aclresult, OBJECT_TABLESPACE,
						   get_tablespace_name(fc_new_tablespaceoid));
	}

	/*
	 * 现在检查完成，检查我们是否应该将其设置为
	 * InvalidOid，因为这是我们数据库的默认表空间。
	 */
	if (fc_orig_tablespaceoid == MyDatabaseTableSpace)
		fc_orig_tablespaceoid = InvalidOid;

	if (fc_new_tablespaceoid == MyDatabaseTableSpace)
		fc_new_tablespaceoid = InvalidOid;

	/* 不执行操作 */
	if (fc_orig_tablespaceoid == fc_new_tablespaceoid)
		return fc_new_tablespaceoid;

	/*
	 * 遍历表空间中的对象列表并移动它们。这将
	 * 当然只会找到我们数据库中的对象。
	 */
	ScanKeyInit(&fc_key[0],
				Anum_pg_class_reltablespace,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_orig_tablespaceoid));

	fc_rel = table_open(RelationRelationId, AccessShareLock);
	fc_scan = table_beginscan_catalog(fc_rel, 1, fc_key);
	while ((fc_tuple = heap_getnext(fc_scan, ForwardScanDirection)) != NULL)
	{
		Form_pg_class fc_relForm = (Form_pg_class) GETSTRUCT(fc_tuple);
		Oid			fc_relOid = fc_relForm->oid;

		/*
		 * 在此过程中不要移动 pg_catalog 中的对象，如果管理员
		 * 确实希望这么做，他们可以直接发出各个 ALTER
		 * 命令。
		 *
		 * 此外，要明确避免任何共享表、临时表或 TOAST
		 * （TOAST 将与主表一起移动）。
		 */
		if (IsCatalogNamespace(fc_relForm->relnamespace) ||
			fc_relForm->relisshared ||
			isAnyTempNamespace(fc_relForm->relnamespace) ||
			IsToastNamespace(fc_relForm->relnamespace))
			continue;

		/* 仅移动请求的对象类型 */
		if ((fc_stmt->objtype == OBJECT_TABLE &&
			 fc_relForm->relkind != RELKIND_RELATION &&
			 fc_relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
			(fc_stmt->objtype == OBJECT_INDEX &&
			 fc_relForm->relkind != RELKIND_INDEX &&
			 fc_relForm->relkind != RELKIND_PARTITIONED_INDEX) ||
			(fc_stmt->objtype == OBJECT_MATVIEW &&
			 fc_relForm->relkind != RELKIND_MATVIEW))
			continue;

		/* 检查我们是否仅移动某些角色拥有的对象 */
		if (fc_role_oids != NIL && !list_member_oid(fc_role_oids, fc_relForm->relowner))
			continue;

		/*
		 * 在这里处理权限检查，因为我们正在锁定表
		 * 还要避免做大量工作而半途而废。请注意
		 * 权限也将由 AlterTableInternal() 进行检查。
		 *
		 * 调用者必须被视为表的所有者才能移动它。
		 */
		if (!pg_class_ownercheck(fc_relOid, GetUserId()))
			aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(fc_relOid)),
						   NameStr(fc_relForm->relname));

		if (fc_stmt->nowait &&
			!ConditionalLockRelationOid(fc_relOid, AccessExclusiveLock))
			ereport(ERROR,
					(errcode(ERRCODE_OBJECT_IN_USE),
					 errmsg("aborting because lock on relation \"%s.%s\" is not available",
							get_namespace_name(fc_relForm->relnamespace),
							NameStr(fc_relForm->relname))));
		else
			LockRelationOid(fc_relOid, AccessExclusiveLock);

		/* 添加到我们要移动的对象列表中 */
		fc_relations = lappend_oid(fc_relations, fc_relOid);
	}

	table_endscan(fc_scan);
	table_close(fc_rel, AccessShareLock);

	if (fc_relations == NIL)
		ereport(NOTICE,
				(errcode(ERRCODE_NO_DATA_FOUND),
				 errmsg("no matching relations in tablespace \"%s\" found",
						fc_orig_tablespaceoid == InvalidOid ? "(database default)" :
						get_tablespace_name(fc_orig_tablespaceoid))));

	/* 一切都被锁定，遍历并移动所有关系。 */
	foreach(fc_l, fc_relations)
	{
		List	   *fc_cmds = NIL;
		AlterTableCmd *fc_cmd = makeNode(AlterTableCmd);

		fc_cmd->subtype = AT_SetTableSpace;
		fc_cmd->name = fc_stmt->new_tablespacename;

		fc_cmds = lappend(fc_cmds, fc_cmd);

		EventTriggerAlterTableStart((Node *) fc_stmt);
		/* OID 由 AlterTableInternal 设置 */
		AlterTableInternal(lfirst_oid(fc_l), fc_cmds, false);
		EventTriggerAlterTableEnd();
	}

	return fc_new_tablespaceoid;
}

static void fc_index_copy_data(Relation fc_rel, RelFileNode fc_newrnode)
{
	SMgrRelation fc_dstrel;

	fc_dstrel = smgropen(fc_newrnode, fc_rel->rd_backend);

	/*
	 * 因为我们直接复制文件而不查看共享缓冲区，
	 * 我们最好首先刷新源关系在共享缓冲区中的任何页。我们假设
	 * 在我们持有该关系的专有锁时不会做任何新更改。
	 */
	FlushRelationBuffers(fc_rel);

	/*
	 * 创建和复制关系的所有文件，并计划 unlink 旧的物理文件。
	 *
	 * 注意：任何在 relfilenode 值上的冲突将在
	 * RelationCreateStorage() 中被捕获。
	 */
	RelationCreateStorage(fc_newrnode, fc_rel->rd_rel->relpersistence, true);

	/* 复制主文件 */
	RelationCopyStorage(RelationGetSmgr(fc_rel), fc_dstrel, MAIN_FORKNUM,
						fc_rel->rd_rel->relpersistence);

	/* 复制存在的额外文件 */
	for (ForkNumber fc_forkNum = MAIN_FORKNUM + 1;
		 fc_forkNum <= MAX_FORKNUM; fc_forkNum++)
	{
		if (smgrexists(RelationGetSmgr(fc_rel), fc_forkNum))
		{
			smgrcreate(fc_dstrel, fc_forkNum, false);

			/*
			 * 如果关系是持久的，或者这是未记录关系的初始化文件，
			 * 则创建 WAL 日志。
			 */
			if (RelationIsPermanent(fc_rel) ||
				(fc_rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED &&
				 fc_forkNum == INIT_FORKNUM))
				log_smgrcreate(&fc_newrnode, fc_forkNum);
			RelationCopyStorage(RelationGetSmgr(fc_rel), fc_dstrel, fc_forkNum,
								fc_rel->rd_rel->relpersistence);
		}
	}

	/* 删除旧关系，关闭新关系 */
	RelationDropStorage(fc_rel);
	smgrclose(fc_dstrel);
}

/*
 * ALTER TABLE ENABLE/DISABLE TRIGGER
 *
 * 我们只需将其委托给 trigger.c。
 */
static void fc_ATExecEnableDisableTrigger(Relation fc_rel, const char *fc_trigname,
						   char fc_fires_when, bool fc_skip_system, bool fc_recurse,
						   LOCKMODE fc_lockmode)
{
	EnableDisableTriggerNew2(fc_rel, fc_trigname, InvalidOid,
							 fc_fires_when, fc_skip_system, fc_recurse,
							 fc_lockmode);
}

/*
 * ALTER TABLE ENABLE/DISABLE RULE
 *
 * 我们只需将其委托给 rewriteDefine.c。
 */
static void fc_ATExecEnableDisableRule(Relation fc_rel, const char *fc_rulename,
						char fc_fires_when, LOCKMODE fc_lockmode)
{
	EnableDisableRule(fc_rel, fc_rulename, fc_fires_when);
}

/*
 * ALTER TABLE INHERIT
 *
 * 向子表的父表添加一个父级。这验证了所有列和
 * 父表的检查约束在子表中出现，并且它们具有相同的数据类型和表达式。
 */
static void fc_ATPrepAddInherit(Relation fc_child_rel)
{
	if (fc_child_rel->rd_rel->reloftype)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("cannot change inheritance of typed table")));

	if (fc_child_rel->rd_rel->relispartition)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("cannot change inheritance of a partition")));

	if (fc_child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("cannot change inheritance of partitioned table")));
}

/*
 * 返回新父关系的地址。
 */
static ObjectAddress fc_ATExecAddInherit(Relation fc_child_rel, RangeVar *fc_parent, LOCKMODE fc_lockmode)
{
	Relation	fc_parent_rel;
	List	   *fc_children;
	ObjectAddress fc_address;
	const char *fc_trigger_name;

	/*
	 * 这里需要一个自我排他锁。有关完整解释，请参见
	 * MergeAttributes() 中的类似情况。
	 */
	fc_parent_rel = table_openrv(fc_parent, ShareUpdateExclusiveLock);

	/*
	 * 必须是父表和子表的所有者--子表通过
	 * ATPrepCmd 中的 ATSimplePermissions 调用进行了检查
	 */
	fc_ATSimplePermissions(AT_AddInherit, fc_parent_rel, ATT_TABLE | ATT_FOREIGN_TABLE);

	/* 永久关系不能从临时关系中继承 */
	if (fc_parent_rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
		fc_child_rel->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("cannot inherit from temporary relation \"%s\"",
						RelationGetRelationName(fc_parent_rel))));

	/* 如果父关系是临时，它必须属于该会话 */
	if (fc_parent_rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
		!fc_parent_rel->rd_islocaltemp)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("cannot inherit from temporary relation of another session")));

	/* Ditto for the child */
	if (fc_child_rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
		!fc_child_rel->rd_islocaltemp)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("cannot inherit to temporary relation of another session")));

	/* 防止分区表成为继承父表 */
	if (fc_parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("cannot inherit from partitioned table \"%s\"",
						fc_parent->relname)));

	/* 同样适用于分区 */
	if (fc_parent_rel->rd_rel->relispartition)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("cannot inherit from a partition")));

	/*
	 * 通过检查提议的父表是否继承自子表来防止循环。
	 * （特别是，这不允许使关系继承自自身。）
	 *
	 * 由于竞态条件，这并不是完全可靠：在多级继承树中，其他人可能同时创建另一个继承链接，这个链接会形成一个闭环，但不连接我们已锁定的任何关系。 防止这种情况似乎需要对整个继承树施加独占锁，这比疾病更糟糕。 find_all_inheritors() 无论如何会处理循环问题，所以不必太担心。
	 *
	 * 我们在子表的子项上使用的锁是最弱的，即 AccessShareLock。
	 */
	fc_children = find_all_inheritors(RelationGetRelid(fc_child_rel),
								   AccessShareLock, NULL);

	if (list_member_oid(fc_children, RelationGetRelid(fc_parent_rel)))
		ereport(ERROR,
				(errcode(ERRCODE_DUPLICATE_TABLE),
				 errmsg("circular inheritance not allowed"),
				 errdetail("\"%s\" is already a child of \"%s\".",
						   fc_parent->relname,
						   RelationGetRelationName(fc_child_rel))));

	/*
	 * 如果 child_rel 具有行级触发器和过渡表，目前不允许其成为继承子表。 另见 ATExecAttachPartition() 和 CreateTrigger() 中的禁忌。
	 */
	fc_trigger_name = FindTriggerIncompatibleWithInheritance(fc_child_rel->trigdesc);
	if (fc_trigger_name != NULL)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("trigger \"%s\" prevents table \"%s\" from becoming an inheritance child",
						fc_trigger_name, RelationGetRelationName(fc_child_rel)),
				 errdetail("ROW triggers with transition tables are not supported in inheritance hierarchies.")));

	/* 创建继承是可以的 */
	fc_CreateInheritance(fc_child_rel, fc_parent_rel);

	ObjectAddressSet(fc_address, RelationRelationId,
					 RelationGetRelid(fc_parent_rel));

	/* 在提交之前保持对父关系的锁定 */
	table_close(fc_parent_rel, NoLock);

	return fc_address;
}

/*
 * CreateInheritance
 *		创建子表与父表之间继承的目录操作部分。
 *
 * 通用与 ATExecAddInherit() 和 ATExecAttachPartition()。
 */
static void fc_CreateInheritance(Relation fc_child_rel, Relation fc_parent_rel)
{
	Relation	fc_catalogRelation;
	SysScanDesc fc_scan;
	ScanKeyData fc_key;
	HeapTuple	fc_inheritsTuple;
	int32		fc_inhseqno;

	/* 注意：获取 RowExclusiveLock，因为我们将在下面写入 pg_inherits。 */
	fc_catalogRelation = table_open(InheritsRelationId, RowExclusiveLock);

	/*
	 * 检查父表列表中的重复项，并确定已经存在的最高 inhseqno；我们将使用下一个作为新父表。
	 * 此外，如果提议的子表是一个分区，则它不能已经在继承中。
	 *
	 * 注意：我们不拒绝子表间接继承父表的情况；CREATE TABLE 不会拒绝可比较的情况。
	 */
	ScanKeyInit(&fc_key,
				Anum_pg_inherits_inhrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(RelationGetRelid(fc_child_rel)));
	fc_scan = systable_beginscan(fc_catalogRelation, InheritsRelidSeqnoIndexId,
							  true, NULL, 1, &fc_key);

	/* inhseqno 序列以 1 开始 */
	fc_inhseqno = 0;
	while (HeapTupleIsValid(fc_inheritsTuple = systable_getnext(fc_scan)))
	{
		Form_pg_inherits fc_inh = (Form_pg_inherits) GETSTRUCT(fc_inheritsTuple);

		if (fc_inh->inhparent == RelationGetRelid(fc_parent_rel))
			ereport(ERROR,
					(errcode(ERRCODE_DUPLICATE_TABLE),
					 errmsg("relation \"%s\" would be inherited from more than once",
							RelationGetRelationName(fc_parent_rel))));

		if (fc_inh->inhseqno > fc_inhseqno)
			fc_inhseqno = fc_inh->inhseqno;
	}
	systable_endscan(fc_scan);

	/* 匹配列并根据需要增加 attinhcount */
	fc_MergeAttributesIntoExisting(fc_child_rel, fc_parent_rel);

	/* 匹配约束并根据需要增加 coninhcount */
	fc_MergeConstraintsIntoExisting(fc_child_rel, fc_parent_rel);

	/*
	 * 好的，看起来有效。 创建显示继承的目录条目。
	 */
	fc_StoreCatalogInheritance1(RelationGetRelid(fc_child_rel),
							 RelationGetRelid(fc_parent_rel),
							 fc_inhseqno + 1,
							 fc_catalogRelation,
							 fc_parent_rel->rd_rel->relkind ==
							 RELKIND_PARTITIONED_TABLE);

	/* 现在我们完成了 pg_inherits */
	table_close(fc_catalogRelation, RowExclusiveLock);
}

/*
 * 获取给定其 pg_constraint 元组的检查约束的约束表达式的源文本形式
 */
static char * fc_decompile_conbin(HeapTuple fc_contup, TupleDesc fc_tupdesc)
{
	Form_pg_constraint fc_con;
	bool		fc_isnull;
	Datum		fc_attr;
	Datum		fc_expr;

	fc_con = (Form_pg_constraint) GETSTRUCT(fc_contup);
	fc_attr = heap_getattr(fc_contup, Anum_pg_constraint_conbin, fc_tupdesc, &fc_isnull);
	if (fc_isnull)
		elog(ERROR, "null conbin for constraint %u", fc_con->oid);

	fc_expr = DirectFunctionCall2(pg_get_expr, fc_attr,
							   ObjectIdGetDatum(fc_con->conrelid));
	return TextDatumGetCString(fc_expr);
}

/*
 * 确定两个检查约束在功能上是否等价
 *
 * 我们应用的测试是查看它们是否反向编译为相同的源字符串。 这使我们免受诸如属性在父子关系中的物理列号是否相同等问题的影响。
 */
static bool fc_constraints_equivalent(HeapTuple fc_a, HeapTuple fc_b, TupleDesc fc_tupleDesc)
{
	Form_pg_constraint fc_acon = (Form_pg_constraint) GETSTRUCT(fc_a);
	Form_pg_constraint fc_bcon = (Form_pg_constraint) GETSTRUCT(fc_b);

	if (fc_acon->condeferrable != fc_bcon->condeferrable ||
		fc_acon->condeferred != fc_bcon->condeferred ||
		strcmp(fc_decompile_conbin(fc_a, fc_tupleDesc),
			   fc_decompile_conbin(fc_b, fc_tupleDesc)) != 0)
		return false;
	else
		return true;
}

/*
 * 检查子表中的列是否与父表中的列匹配，并增加它们的 attinhcount。
 *
 * 由 CreateInheritance 调用
 *
 * 目前必须在子表中找到所有父表列。缺失的列是一个错误。将来我们可能会考虑像 CREATE TABLE 一样创建新列。然而，这在常见的分区表使用情况下是极不受欢迎的——这是一个自伤。
 *
 * 数据类型必须完全匹配。如果父列为 NOT NULL，则子列也必须为 NOT NULL。默认值不被比较。
 */
static void fc_MergeAttributesIntoExisting(Relation fc_child_rel, Relation fc_parent_rel)
{
	Relation	fc_attrrel;
	AttrNumber	fc_parent_attno;
	int			fc_parent_natts;
	TupleDesc	fc_tupleDesc;
	HeapTuple	fc_tuple;
	bool		fc_child_is_partition = false;

	fc_attrrel = table_open(AttributeRelationId, RowExclusiveLock);

	fc_tupleDesc = RelationGetDescr(fc_parent_rel);
	fc_parent_natts = fc_tupleDesc->natts;

	/* 如果 parent_rel 是一个分区表，child_rel 必须是一个分区 */
	if (fc_parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
		fc_child_is_partition = true;

	for (fc_parent_attno = 1; fc_parent_attno <= fc_parent_natts; fc_parent_attno++)
	{
		Form_pg_attribute fc_attribute = TupleDescAttr(fc_tupleDesc,
													fc_parent_attno - 1);
		char	   *fc_attributeName = NameStr(fc_attribute->attname);

		/* 忽略父表中删除的列。 */
		if (fc_attribute->attisdropped)
			continue;

		/* 在子表中查找相同的列（按列名匹配）。 */
		fc_tuple = SearchSysCacheCopyAttName(RelationGetRelid(fc_child_rel),
										  fc_attributeName);
		if (HeapTupleIsValid(fc_tuple))
		{
			/* 检查它们是否为相同类型、typmod 和 collation */
			Form_pg_attribute fc_childatt = (Form_pg_attribute) GETSTRUCT(fc_tuple);

			if (fc_attribute->atttypid != fc_childatt->atttypid ||
				fc_attribute->atttypmod != fc_childatt->atttypmod)
				ereport(ERROR,
						(errcode(ERRCODE_DATATYPE_MISMATCH),
						 errmsg("child table \"%s\" has different type for column \"%s\"",
								RelationGetRelationName(fc_child_rel),
								fc_attributeName)));

			if (fc_attribute->attcollation != fc_childatt->attcollation)
				ereport(ERROR,
						(errcode(ERRCODE_COLLATION_MISMATCH),
						 errmsg("child table \"%s\" has different collation for column \"%s\"",
								RelationGetRelationName(fc_child_rel),
								fc_attributeName)));

			/*
			 * 检查子表未丢弃 NOT NULL 属性。（其他约束在其他地方检查。）
			 */
			if (fc_attribute->attnotnull && !fc_childatt->attnotnull)
				ereport(ERROR,
						(errcode(ERRCODE_DATATYPE_MISMATCH),
						 errmsg("column \"%s\" in child table must be marked NOT NULL",
								fc_attributeName)));

			/*
			 * 如果父列是生成的，子列也必须是生成的。
			 */
			if (fc_attribute->attgenerated && !fc_childatt->attgenerated)
				ereport(ERROR,
						(errcode(ERRCODE_DATATYPE_MISMATCH),
						 errmsg("column \"%s\" in child table must be a generated column",
								fc_attributeName)));

			/*
			 * 检查两个生成表达式是否匹配。
			 *
			 * 我们应用的测试是观察它们是否反向编译为相同的源字符串。这使我们免受属性在父关系和子关系中是否具有相同物理列号等问题的干扰。（另见 constraints_equivalent()。）
			 */
			if (fc_attribute->attgenerated && fc_childatt->attgenerated)
			{
				TupleConstr *fc_child_constr = fc_child_rel->rd_att->constr;
				TupleConstr *fc_parent_constr = fc_parent_rel->rd_att->constr;
				char	   *fc_child_expr = NULL;
				char	   *fc_parent_expr = NULL;

				Assert(fc_child_constr != NULL);
				Assert(fc_parent_constr != NULL);

				for (int fc_i = 0; fc_i < fc_child_constr->num_defval; fc_i++)
				{
					if (fc_child_constr->defval[fc_i].adnum == fc_childatt->attnum)
					{
						fc_child_expr =
							TextDatumGetCString(DirectFunctionCall2(pg_get_expr,
																	CStringGetTextDatum(fc_child_constr->defval[fc_i].adbin),
																	ObjectIdGetDatum(fc_child_rel->rd_id)));
						break;
					}
				}
				Assert(fc_child_expr != NULL);

				for (int fc_i = 0; fc_i < fc_parent_constr->num_defval; fc_i++)
				{
					if (fc_parent_constr->defval[fc_i].adnum == fc_attribute->attnum)
					{
						fc_parent_expr =
							TextDatumGetCString(DirectFunctionCall2(pg_get_expr,
																	CStringGetTextDatum(fc_parent_constr->defval[fc_i].adbin),
																	ObjectIdGetDatum(fc_parent_rel->rd_id)));
						break;
					}
				}
				Assert(fc_parent_expr != NULL);

				if (strcmp(fc_child_expr, fc_parent_expr) != 0)
					ereport(ERROR,
							(errcode(ERRCODE_DATATYPE_MISMATCH),
							 errmsg("column \"%s\" in child table has a conflicting generation expression",
									fc_attributeName)));
			}

			/*
			 * 好吧，增加子列的继承计数。（如果我们稍后失败，此更改将只会回滚。）
			 */
			fc_childatt->attinhcount++;

			/*
			 * 在分区的情况下，我们必须确保所有分区中的 attislocal 值相同。（注意：分区中只存在继承属性）
			 */
			if (fc_child_is_partition)
			{
				Assert(fc_childatt->attinhcount == 1);
				fc_childatt->attislocal = false;
			}

			CatalogTupleUpdate(fc_attrrel, &fc_tuple->t_self, fc_tuple);
			heap_freetuple(fc_tuple);
		}
		else
		{
			ereport(ERROR,
					(errcode(ERRCODE_DATATYPE_MISMATCH),
					 errmsg("child table is missing column \"%s\"",
							fc_attributeName)));
		}
	}

	table_close(fc_attrrel, RowExclusiveLock);
}

/*
 * 检查子表中的约束是否与父表中的约束匹配，并增加它们的 coninhcount。
 *
 * 在父表中仅标记的约束将被忽略。
 *
 * 由 CreateInheritance 调用
 *
 * 目前父表中的所有约束必须在子表中存在。将来我们可能会考虑像 CREATE TABLE 一样添加新约束。
 *
 * XXX 这是 O(N^2)，这可能是存在数百个约束的表的问题。只要表的约束数大约为 10，应该没有问题。即使是 100 个约束也不应该是世界末日。
 *
 * XXX 如果您更改此代码，请查看 MergeWithExistingConstraint。
 */
static void fc_MergeConstraintsIntoExisting(Relation fc_child_rel, Relation fc_parent_rel)
{
	Relation	fc_catalog_relation;
	TupleDesc	fc_tuple_desc;
	SysScanDesc fc_parent_scan;
	ScanKeyData fc_parent_key;
	HeapTuple	fc_parent_tuple;
	bool		fc_child_is_partition = false;

	fc_catalog_relation = table_open(ConstraintRelationId, RowExclusiveLock);
	fc_tuple_desc = RelationGetDescr(fc_catalog_relation);

	/* 如果 parent_rel 是一个分区表，child_rel 必须是一个分区 */
	if (fc_parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
		fc_child_is_partition = true;

	/* 外部循环扫描父表的约束定义 */
	ScanKeyInit(&fc_parent_key,
				Anum_pg_constraint_conrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(RelationGetRelid(fc_parent_rel)));
	fc_parent_scan = systable_beginscan(fc_catalog_relation, ConstraintRelidTypidNameIndexId,
									 true, NULL, 1, &fc_parent_key);

	while (HeapTupleIsValid(fc_parent_tuple = systable_getnext(fc_parent_scan)))
	{
		Form_pg_constraint fc_parent_con = (Form_pg_constraint) GETSTRUCT(fc_parent_tuple);
		SysScanDesc fc_child_scan;
		ScanKeyData fc_child_key;
		HeapTuple	fc_child_tuple;
		bool		fc_found = false;

		if (fc_parent_con->contype != CONSTRAINT_CHECK)
			continue;

		/* 如果父表的约束标记为 NO INHERIT，则它不被继承 */
		if (fc_parent_con->connoinherit)
			continue;

		/* 查找与此约束匹配的子约束 */
		ScanKeyInit(&fc_child_key,
					Anum_pg_constraint_conrelid,
					BTEqualStrategyNumber, F_OIDEQ,
					ObjectIdGetDatum(RelationGetRelid(fc_child_rel)));
		fc_child_scan = systable_beginscan(fc_catalog_relation, ConstraintRelidTypidNameIndexId,
										true, NULL, 1, &fc_child_key);

		while (HeapTupleIsValid(fc_child_tuple = systable_getnext(fc_child_scan)))
		{
			Form_pg_constraint fc_child_con = (Form_pg_constraint) GETSTRUCT(fc_child_tuple);
			HeapTuple	fc_child_copy;

			if (fc_child_con->contype != CONSTRAINT_CHECK)
				continue;

			if (strcmp(NameStr(fc_parent_con->conname),
					   NameStr(fc_child_con->conname)) != 0)
				continue;

			if (!fc_constraints_equivalent(fc_parent_tuple, fc_child_tuple, fc_tuple_desc))
				ereport(ERROR,
						(errcode(ERRCODE_DATATYPE_MISMATCH),
						 errmsg("child table \"%s\" has different definition for check constraint \"%s\"",
								RelationGetRelationName(fc_child_rel),
								NameStr(fc_parent_con->conname))));

			/* 如果子约束是“不可继承”，则无法合并 */
			if (fc_child_con->connoinherit)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
						 errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
								NameStr(fc_child_con->conname),
								RelationGetRelationName(fc_child_rel))));

			/*
			 * 如果子约束是“无效”，则无法与有效的父约束合并
			 */
			if (fc_parent_con->convalidated && !fc_child_con->convalidated)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
						 errmsg("constraint \"%s\" conflicts with NOT VALID constraint on child table \"%s\"",
								NameStr(fc_child_con->conname),
								RelationGetRelationName(fc_child_rel))));

			/*
			 * 好吧，增加子约束的继承计数。（如果我们稍后失败，此更改将只会回滚。）
			 */
			fc_child_copy = heap_copytuple(fc_child_tuple);
			fc_child_con = (Form_pg_constraint) GETSTRUCT(fc_child_copy);
			fc_child_con->coninhcount++;

			/*
			 * 在分区的情况下，继承约束只能被继承一次，因为它不能有多个父对象，并且它永远不会被视为局部的。
			 */
			if (fc_child_is_partition)
			{
				Assert(fc_child_con->coninhcount == 1);
				fc_child_con->conislocal = false;
			}

			CatalogTupleUpdate(fc_catalog_relation, &fc_child_copy->t_self, fc_child_copy);
			heap_freetuple(fc_child_copy);

			fc_found = true;
			break;
		}

		systable_endscan(fc_child_scan);

		if (!fc_found)
			ereport(ERROR,
					(errcode(ERRCODE_DATATYPE_MISMATCH),
					 errmsg("child table is missing constraint \"%s\"",
							NameStr(fc_parent_con->conname))));
	}

	systable_endscan(fc_parent_scan);
	table_close(fc_catalog_relation, RowExclusiveLock);
}

/*
 * ALTER TABLE NO INHERIT
 *
 * 返回值是不再作为父对象的关系的地址。
 */
static ObjectAddress fc_ATExecDropInherit(Relation fc_rel, RangeVar *fc_parent, LOCKMODE fc_lockmode)
{
	ObjectAddress fc_address;
	Relation	fc_parent_rel;

	if (fc_rel->rd_rel->relispartition)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("cannot change inheritance of a partition")));

	/*
	 * 对父对象加上AccessShareLock可能已经足够，因为DROP
	 * TABLE根本不会锁定父表。我们需要某种锁，因为我们要检查父对象的模式。
	 */
	fc_parent_rel = table_openrv(fc_parent, AccessShareLock);

	/*
	 * 我们不检查父表的所有权——子表的所有权被假定为有足够的权限。
	 */

	/* 进入RemoveInheritance()，大部分工作在这里进行 */
	fc_RemoveInheritance(fc_rel, fc_parent_rel, false);

	ObjectAddressSet(fc_address, RelationRelationId,
					 RelationGetRelid(fc_parent_rel));

	/* 在提交之前保持对父关系的锁定 */
	table_close(fc_parent_rel, NoLock);

	return fc_address;
}

/*
 * MarkInheritDetached
 *
 * 在并发模式下为ATExecDetachPartition设置分区的inhdetachpending。与此同时，验证没有其他分区已经处于待分离状态。
 */
static void fc_MarkInheritDetached(Relation fc_child_rel, Relation fc_parent_rel)
{
	Relation	fc_catalogRelation;
	SysScanDesc fc_scan;
	ScanKeyData fc_key;
	HeapTuple	fc_inheritsTuple;
	bool		fc_found = false;

	Assert(fc_parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);

	/*
	 * 通过inhparent查找pg_inherits条目。 （我们需要扫描它们全部，以验证没有其他分区处于待分离状态。）
	 */
	fc_catalogRelation = table_open(InheritsRelationId, RowExclusiveLock);
	ScanKeyInit(&fc_key,
				Anum_pg_inherits_inhparent,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(RelationGetRelid(fc_parent_rel)));
	fc_scan = systable_beginscan(fc_catalogRelation, InheritsParentIndexId,
							  true, NULL, 1, &fc_key);

	while (HeapTupleIsValid(fc_inheritsTuple = systable_getnext(fc_scan)))
	{
		Form_pg_inherits fc_inhForm;

		fc_inhForm = (Form_pg_inherits) GETSTRUCT(fc_inheritsTuple);
		if (fc_inhForm->inhdetachpending)
			ereport(ERROR,
					errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
					errmsg("partition \"%s\" already pending detach in partitioned table \"%s.%s\"",
						   get_rel_name(fc_inhForm->inhrelid),
						   get_namespace_name(fc_parent_rel->rd_rel->relnamespace),
						   RelationGetRelationName(fc_parent_rel)),
					errhint("Use ALTER TABLE ... DETACH PARTITION ... FINALIZE to complete the pending detach operation."));

		if (fc_inhForm->inhrelid == RelationGetRelid(fc_child_rel))
		{
			HeapTuple	fc_newtup;

			fc_newtup = heap_copytuple(fc_inheritsTuple);
			((Form_pg_inherits) GETSTRUCT(fc_newtup))->inhdetachpending = true;

			CatalogTupleUpdate(fc_catalogRelation,
							   &fc_inheritsTuple->t_self,
							   fc_newtup);
			fc_found = true;
			heap_freetuple(fc_newtup);
			/* 继续查找，以确保捕捉到其他待分离的条目 */
		}
	}

	/* 完成 */
	systable_endscan(fc_scan);
	table_close(fc_catalogRelation, RowExclusiveLock);

	if (!fc_found)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_TABLE),
				 errmsg("relation \"%s\" is not a partition of relation \"%s\"",
						RelationGetRelationName(fc_child_rel),
						RelationGetRelationName(fc_parent_rel))));
}

/*
 * RemoveInheritance
 *
 * 从子表的父对象中删除一个父对象。这只需调整列的attinhcount
 * 和attislocal，并删除pg_inherit和pg_depend
 * 条目。expect_detached被传递给DeleteInheritsTuple，参见。
 *
 * 如果attinhcount变为0，则attislocal被设置为true。如果它又上升
 * 则attislocal保持为true，这意味着如果子对象从父对象中移除
 * 那么它的列将永远不会被自动删除，这可能会令人惊讶。然而，至少
 * 我们永远不会通过删除某人不期望被删除的列而惊讶，这实际上
 * 意味着数据丢失。
 *
 * 继承约束的coninhcount和conislocal也以完全相同的方式调整。
 *
 * 这对于ATExecDropInherit()和ATExecDetachPartition()是共同的。
 */
static void fc_RemoveInheritance(Relation fc_child_rel, Relation fc_parent_rel, bool fc_expect_detached)
{
	Relation	fc_catalogRelation;
	SysScanDesc fc_scan;
	ScanKeyData fc_key[3];
	HeapTuple	fc_attributeTuple,
				fc_constraintTuple;
	List	   *fc_connames;
	bool		fc_found;
	bool		fc_child_is_partition = false;

	/* 如果 parent_rel 是一个分区表，child_rel 必须是一个分区 */
	if (fc_parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
		fc_child_is_partition = true;

	fc_found = DeleteInheritsTuple(RelationGetRelid(fc_child_rel),
								RelationGetRelid(fc_parent_rel),
								fc_expect_detached,
								RelationGetRelationName(fc_child_rel));
	if (!fc_found)
	{
		if (fc_child_is_partition)
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_TABLE),
					 errmsg("relation \"%s\" is not a partition of relation \"%s\"",
							RelationGetRelationName(fc_child_rel),
							RelationGetRelationName(fc_parent_rel))));
		else
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_TABLE),
					 errmsg("relation \"%s\" is not a parent of relation \"%s\"",
							RelationGetRelationName(fc_parent_rel),
							RelationGetRelationName(fc_child_rel))));
	}

	/*
	 * 在子列中搜索与父对象匹配的列
	 */
	fc_catalogRelation = table_open(AttributeRelationId, RowExclusiveLock);
	ScanKeyInit(&fc_key[0],
				Anum_pg_attribute_attrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(RelationGetRelid(fc_child_rel)));
	fc_scan = systable_beginscan(fc_catalogRelation, AttributeRelidNumIndexId,
							  true, NULL, 1, fc_key);
	while (HeapTupleIsValid(fc_attributeTuple = systable_getnext(fc_scan)))
	{
		Form_pg_attribute fc_att = (Form_pg_attribute) GETSTRUCT(fc_attributeTuple);

		/* 如果已删除或未继承则忽略 */
		if (fc_att->attisdropped)
			continue;
		if (fc_att->attinhcount <= 0)
			continue;

		if (SearchSysCacheExistsAttName(RelationGetRelid(fc_parent_rel),
										NameStr(fc_att->attname)))
		{
			/* 减少inhcount，并可能将islocal设置为true */
			HeapTuple	fc_copyTuple = heap_copytuple(fc_attributeTuple);
			Form_pg_attribute fc_copy_att = (Form_pg_attribute) GETSTRUCT(fc_copyTuple);

			fc_copy_att->attinhcount--;
			if (fc_copy_att->attinhcount == 0)
				fc_copy_att->attislocal = true;

			CatalogTupleUpdate(fc_catalogRelation, &fc_copyTuple->t_self, fc_copyTuple);
			heap_freetuple(fc_copyTuple);
		}
	}
	systable_endscan(fc_scan);
	table_close(fc_catalogRelation, RowExclusiveLock);

	/*
	 * 同样，查找继承的检查约束并取消继承。要做到这一点，我们首先需要一个父检查
	 * 约束名称的列表。 （我们通过仅检查名称匹配来进行了一点作弊，
	 * 假设表达式会匹配。）
	 */
	fc_catalogRelation = table_open(ConstraintRelationId, RowExclusiveLock);
	ScanKeyInit(&fc_key[0],
				Anum_pg_constraint_conrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(RelationGetRelid(fc_parent_rel)));
	fc_scan = systable_beginscan(fc_catalogRelation, ConstraintRelidTypidNameIndexId,
							  true, NULL, 1, fc_key);

	fc_connames = NIL;

	while (HeapTupleIsValid(fc_constraintTuple = systable_getnext(fc_scan)))
	{
		Form_pg_constraint fc_con = (Form_pg_constraint) GETSTRUCT(fc_constraintTuple);

		if (fc_con->contype == CONSTRAINT_CHECK)
			fc_connames = lappend(fc_connames, pstrdup(NameStr(fc_con->conname)));
	}

	systable_endscan(fc_scan);

	/* 现在扫描子对象的约束 */
	ScanKeyInit(&fc_key[0],
				Anum_pg_constraint_conrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(RelationGetRelid(fc_child_rel)));
	fc_scan = systable_beginscan(fc_catalogRelation, ConstraintRelidTypidNameIndexId,
							  true, NULL, 1, fc_key);

	while (HeapTupleIsValid(fc_constraintTuple = systable_getnext(fc_scan)))
	{
		Form_pg_constraint fc_con = (Form_pg_constraint) GETSTRUCT(fc_constraintTuple);
		bool		fc_match;
		ListCell   *fc_lc;

		if (fc_con->contype != CONSTRAINT_CHECK)
			continue;

		fc_match = false;
		foreach(fc_lc, fc_connames)
		{
			if (strcmp(NameStr(fc_con->conname), (char *) lfirst(fc_lc)) == 0)
			{
				fc_match = true;
				break;
			}
		}

		if (fc_match)
		{
			/* 减少inhcount，并可能将islocal设置为true */
			HeapTuple	fc_copyTuple = heap_copytuple(fc_constraintTuple);
			Form_pg_constraint fc_copy_con = (Form_pg_constraint) GETSTRUCT(fc_copyTuple);

			if (fc_copy_con->coninhcount <= 0) /* 不应该发生 */
				elog(ERROR, "relation %u has non-inherited constraint \"%s\"",
					 RelationGetRelid(fc_child_rel), NameStr(fc_copy_con->conname));

			fc_copy_con->coninhcount--;
			if (fc_copy_con->coninhcount == 0)
				fc_copy_con->conislocal = true;

			CatalogTupleUpdate(fc_catalogRelation, &fc_copyTuple->t_self, fc_copyTuple);
			heap_freetuple(fc_copyTuple);
		}
	}

	systable_endscan(fc_scan);
	table_close(fc_catalogRelation, RowExclusiveLock);

	fc_drop_parent_dependency(RelationGetRelid(fc_child_rel),
						   RelationRelationId,
						   RelationGetRelid(fc_parent_rel),
						   child_dependency_type(fc_child_is_partition));

	/*
	 * 这个继承的后置修改钩子。由于object_access_hook不接受
	 * 多个对象标识符，故我们通过辅助_id参数中继父关系的oid。
	 */
	InvokeObjectPostAlterHookArg(InheritsRelationId,
								 RelationGetRelid(fc_child_rel), 0,
								 RelationGetRelid(fc_parent_rel), false);
}

/*
 * 删除由 StoreCatalogInheritance1 创建的依赖关系 (CREATE TABLE
 * INHERITS/ALTER TABLE INHERIT -- refclassid 将是 RelationRelationId) 或
 * heap_create_with_catalog (CREATE TABLE OF/ALTER TABLE OF -- refclassid 将
 * 是 TypeRelationId)。 目前没有方便的方法来做到这一点，因此需要
 * 通过 pg_depend 进行查找。
 */
static void fc_drop_parent_dependency(Oid fc_relid, Oid fc_refclassid, Oid fc_refobjid,
					   DependencyType fc_deptype)
{
	Relation	fc_catalogRelation;
	SysScanDesc fc_scan;
	ScanKeyData fc_key[3];
	HeapTuple	fc_depTuple;

	fc_catalogRelation = table_open(DependRelationId, RowExclusiveLock);

	ScanKeyInit(&fc_key[0],
				Anum_pg_depend_classid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(RelationRelationId));
	ScanKeyInit(&fc_key[1],
				Anum_pg_depend_objid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_relid));
	ScanKeyInit(&fc_key[2],
				Anum_pg_depend_objsubid,
				BTEqualStrategyNumber, F_INT4EQ,
				Int32GetDatum(0));

	fc_scan = systable_beginscan(fc_catalogRelation, DependDependerIndexId, true,
							  NULL, 3, fc_key);

	while (HeapTupleIsValid(fc_depTuple = systable_getnext(fc_scan)))
	{
		Form_pg_depend fc_dep = (Form_pg_depend) GETSTRUCT(fc_depTuple);

		if (fc_dep->refclassid == fc_refclassid &&
			fc_dep->refobjid == fc_refobjid &&
			fc_dep->refobjsubid == 0 &&
			fc_dep->deptype == fc_deptype)
			CatalogTupleDelete(fc_catalogRelation, &fc_depTuple->t_self);
	}

	systable_endscan(fc_scan);
	table_close(fc_catalogRelation, RowExclusiveLock);
}

/*
 * ALTER TABLE OF
 *
 * 将表附加到复合类型，就好像它是通过 CREATE
 * TABLE OF 创建的。 所有的 attname、atttypid、atttypmod 和 attcollation
 * 必须匹配。 主题表不能有继承父表。 这些限制确保您无法创建仅使用
 * CREATE TABLE OF 无法实现的配置。
 *
 * 将返回类型的地址。
 */
static ObjectAddress fc_ATExecAddOf(Relation fc_rel, const TypeName *fc_ofTypename, LOCKMODE fc_lockmode)
{
	Oid			fc_relid = RelationGetRelid(fc_rel);
	Type		fc_typetuple;
	Form_pg_type fc_typeform;
	Oid			fc_typeid;
	Relation	fc_inheritsRelation,
				fc_relationRelation;
	SysScanDesc fc_scan;
	ScanKeyData fc_key;
	AttrNumber	fc_table_attno,
				fc_type_attno;
	TupleDesc	fc_typeTupleDesc,
				fc_tableTupleDesc;
	ObjectAddress fc_tableobj,
				fc_typeobj;
	HeapTuple	fc_classtuple;

	/* 验证类型。 */
	fc_typetuple = typenameType(NULL, fc_ofTypename, NULL);
	check_of_type(fc_typetuple);
	fc_typeform = (Form_pg_type) GETSTRUCT(fc_typetuple);
	fc_typeid = fc_typeform->oid;

	/* 如果表有任何继承父表则失败。 */
	fc_inheritsRelation = table_open(InheritsRelationId, AccessShareLock);
	ScanKeyInit(&fc_key,
				Anum_pg_inherits_inhrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_relid));
	fc_scan = systable_beginscan(fc_inheritsRelation, InheritsRelidSeqnoIndexId,
							  true, NULL, 1, &fc_key);
	if (HeapTupleIsValid(systable_getnext(fc_scan)))
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("typed tables cannot inherit")));
	systable_endscan(fc_scan);
	table_close(fc_inheritsRelation, AccessShareLock);

	/*
	 * 检查元组描述符的兼容性。 与继承不同，我们
	 * 要求顺序也必须匹配。 但是，attnotnull 不必匹配。
	 */
	fc_typeTupleDesc = lookup_rowtype_tupdesc(fc_typeid, -1);
	fc_tableTupleDesc = RelationGetDescr(fc_rel);
	fc_table_attno = 1;
	for (fc_type_attno = 1; fc_type_attno <= fc_typeTupleDesc->natts; fc_type_attno++)
	{
		Form_pg_attribute fc_type_attr,
					fc_table_attr;
		const char *fc_type_attname,
				   *fc_table_attname;

		/* 获取下一个未删除的类型属性。 */
		fc_type_attr = TupleDescAttr(fc_typeTupleDesc, fc_type_attno - 1);
		if (fc_type_attr->attisdropped)
			continue;
		fc_type_attname = NameStr(fc_type_attr->attname);

		/* 获取下一个未删除的表属性。 */
		do
		{
			if (fc_table_attno > fc_tableTupleDesc->natts)
				ereport(ERROR,
						(errcode(ERRCODE_DATATYPE_MISMATCH),
						 errmsg("table is missing column \"%s\"",
								fc_type_attname)));
			fc_table_attr = TupleDescAttr(fc_tableTupleDesc, fc_table_attno - 1);
			fc_table_attno++;
		} while (fc_table_attr->attisdropped);
		fc_table_attname = NameStr(fc_table_attr->attname);

		/* 比较名称。 */
		if (strncmp(fc_table_attname, fc_type_attname, NAMEDATALEN) != 0)
			ereport(ERROR,
					(errcode(ERRCODE_DATATYPE_MISMATCH),
					 errmsg("table has column \"%s\" where type requires \"%s\"",
							fc_table_attname, fc_type_attname)));

		/* 比较类型。 */
		if (fc_table_attr->atttypid != fc_type_attr->atttypid ||
			fc_table_attr->atttypmod != fc_type_attr->atttypmod ||
			fc_table_attr->attcollation != fc_type_attr->attcollation)
			ereport(ERROR,
					(errcode(ERRCODE_DATATYPE_MISMATCH),
					 errmsg("table \"%s\" has different type for column \"%s\"",
							RelationGetRelationName(fc_rel), fc_type_attname)));
	}
	ReleaseTupleDesc(fc_typeTupleDesc);

	/* 表末尾的任何剩余列最好被删除。 */
	for (; fc_table_attno <= fc_tableTupleDesc->natts; fc_table_attno++)
	{
		Form_pg_attribute fc_table_attr = TupleDescAttr(fc_tableTupleDesc,
													 fc_table_attno - 1);

		if (!fc_table_attr->attisdropped)
			ereport(ERROR,
					(errcode(ERRCODE_DATATYPE_MISMATCH),
					 errmsg("table has extra column \"%s\"",
							NameStr(fc_table_attr->attname))));
	}

	/* 如果表已经被指定类型，删除现有依赖关系。 */
	if (fc_rel->rd_rel->reloftype)
		fc_drop_parent_dependency(fc_relid, TypeRelationId, fc_rel->rd_rel->reloftype,
							   DEPENDENCY_NORMAL);

	/* 记录对新类型的依赖关系。 */
	fc_tableobj.classId = RelationRelationId;
	fc_tableobj.objectId = fc_relid;
	fc_tableobj.objectSubId = 0;
	fc_typeobj.classId = TypeRelationId;
	fc_typeobj.objectId = fc_typeid;
	fc_typeobj.objectSubId = 0;
	recordDependencyOn(&fc_tableobj, &fc_typeobj, DEPENDENCY_NORMAL);

	/* 更新 pg_class.reloftype */
	fc_relationRelation = table_open(RelationRelationId, RowExclusiveLock);
	fc_classtuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(fc_relid));
	if (!HeapTupleIsValid(fc_classtuple))
		elog(ERROR, "cache lookup failed for relation %u", fc_relid);
	((Form_pg_class) GETSTRUCT(fc_classtuple))->reloftype = fc_typeid;
	CatalogTupleUpdate(fc_relationRelation, &fc_classtuple->t_self, fc_classtuple);

	InvokeObjectPostAlterHook(RelationRelationId, fc_relid, 0);

	heap_freetuple(fc_classtuple);
	table_close(fc_relationRelation, RowExclusiveLock);

	ReleaseSysCache(fc_typetuple);

	return fc_typeobj;
}

/*
 * ALTER TABLE NOT OF
 *
 * 从其源类型中分离一个已类型化的表。 只需清除 reloftype 并
 * 删除依赖关系。
 */
static void fc_ATExecDropOf(Relation fc_rel, LOCKMODE fc_lockmode)
{
	Oid			fc_relid = RelationGetRelid(fc_rel);
	Relation	fc_relationRelation;
	HeapTuple	fc_tuple;

	if (!OidIsValid(fc_rel->rd_rel->reloftype))
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("\"%s\" is not a typed table",
						RelationGetRelationName(fc_rel))));

	/*
	 * 我们不打算检查类型的所有权 --- 假定表的所有权已经拥有
	 * 足够的权限。 对类型也不需要锁定。
	 */

	fc_drop_parent_dependency(fc_relid, TypeRelationId, fc_rel->rd_rel->reloftype,
						   DEPENDENCY_NORMAL);

	/* 清除 pg_class.reloftype */
	fc_relationRelation = table_open(RelationRelationId, RowExclusiveLock);
	fc_tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(fc_relid));
	if (!HeapTupleIsValid(fc_tuple))
		elog(ERROR, "cache lookup failed for relation %u", fc_relid);
	((Form_pg_class) GETSTRUCT(fc_tuple))->reloftype = InvalidOid;
	CatalogTupleUpdate(fc_relationRelation, &fc_tuple->t_self, fc_tuple);

	InvokeObjectPostAlterHook(RelationRelationId, fc_relid, 0);

	heap_freetuple(fc_tuple);
	table_close(fc_relationRelation, RowExclusiveLock);
}

/*
 * relation_mark_replica_identity: 更新表的副本标识
 *
 * 如果 ri_type = REPLICA_IDENTITY_INDEX，则 indexOid 必须是适当
 * 索引的 Oid。 否则，它必须是 InvalidOid。
 *
 * 调用者最好在关系上持有独占锁，因为并发运行两个这样的
 * 操作的结果可能不太好。
 */
static void fc_relation_mark_replica_identity(Relation fc_rel, char fc_ri_type, Oid fc_indexOid,
							   bool fc_is_internal)
{
	Relation	fc_pg_index;
	Relation	fc_pg_class;
	HeapTuple	fc_pg_class_tuple;
	HeapTuple	fc_pg_index_tuple;
	Form_pg_class fc_pg_class_form;
	Form_pg_index fc_pg_index_form;
	ListCell   *fc_index;

	/*
	 * 检查 relreplident 是否发生了变化，如果发生变化则进行更新。
	 */
	fc_pg_class = table_open(RelationRelationId, RowExclusiveLock);
	fc_pg_class_tuple = SearchSysCacheCopy1(RELOID,
										 ObjectIdGetDatum(RelationGetRelid(fc_rel)));
	if (!HeapTupleIsValid(fc_pg_class_tuple))
		elog(ERROR, "cache lookup failed for relation \"%s\"",
			 RelationGetRelationName(fc_rel));
	fc_pg_class_form = (Form_pg_class) GETSTRUCT(fc_pg_class_tuple);
	if (fc_pg_class_form->relreplident != fc_ri_type)
	{
		fc_pg_class_form->relreplident = fc_ri_type;
		CatalogTupleUpdate(fc_pg_class, &fc_pg_class_tuple->t_self, fc_pg_class_tuple);
	}
	table_close(fc_pg_class, RowExclusiveLock);
	heap_freetuple(fc_pg_class_tuple);

	/*
	 * 正确更新每个索引的 indisreplident 标志。
	 */
	fc_pg_index = table_open(IndexRelationId, RowExclusiveLock);
	foreach(fc_index, RelationGetIndexList(fc_rel))
	{
		Oid			fc_thisIndexOid = lfirst_oid(fc_index);
		bool		fc_dirty = false;

		fc_pg_index_tuple = SearchSysCacheCopy1(INDEXRELID,
											 ObjectIdGetDatum(fc_thisIndexOid));
		if (!HeapTupleIsValid(fc_pg_index_tuple))
			elog(ERROR, "cache lookup failed for index %u", fc_thisIndexOid);
		fc_pg_index_form = (Form_pg_index) GETSTRUCT(fc_pg_index_tuple);

		if (fc_thisIndexOid == fc_indexOid)
		{
			/* 如果尚未设置则设置该位。 */
			if (!fc_pg_index_form->indisreplident)
			{
				fc_dirty = true;
				fc_pg_index_form->indisreplident = true;
			}
		}
		else
		{
			/* 如果已设置则清除该位。 */
			if (fc_pg_index_form->indisreplident)
			{
				fc_dirty = true;
				fc_pg_index_form->indisreplident = false;
			}
		}

		if (fc_dirty)
		{
			CatalogTupleUpdate(fc_pg_index, &fc_pg_index_tuple->t_self, fc_pg_index_tuple);
			InvokeObjectPostAlterHookArg(IndexRelationId, fc_thisIndexOid, 0,
										 InvalidOid, fc_is_internal);

			/*
			 * 使表的 relcache 失效，以便在我们提交后
			 * 所有会话将在对表进行任何 UPDATE 或 DELETE 之前
			 * 刷新表的副本标识索引。 （如果我们上面更改了表的
			 * pg_class 行，那么由于此原因，relcache inval
			 * 已经排队；但我们可能没有。）
			 */
			CacheInvalidateRelcache(fc_rel);
		}
		heap_freetuple(fc_pg_index_tuple);
	}

	table_close(fc_pg_index, RowExclusiveLock);
}

/*
 * ALTER TABLE <name> REPLICA IDENTITY ...
 */
static void fc_ATExecReplicaIdentity(Relation fc_rel, ReplicaIdentityStmt *fc_stmt, LOCKMODE fc_lockmode)
{
	Oid			fc_indexOid;
	Relation	fc_indexRel;
	int			fc_key;

	if (fc_stmt->identity_type == REPLICA_IDENTITY_DEFAULT)
	{
		fc_relation_mark_replica_identity(fc_rel, fc_stmt->identity_type, InvalidOid, true);
		return;
	}
	else if (fc_stmt->identity_type == REPLICA_IDENTITY_FULL)
	{
		fc_relation_mark_replica_identity(fc_rel, fc_stmt->identity_type, InvalidOid, true);
		return;
	}
	else if (fc_stmt->identity_type == REPLICA_IDENTITY_NOTHING)
	{
		fc_relation_mark_replica_identity(fc_rel, fc_stmt->identity_type, InvalidOid, true);
		return;
	}
	else if (fc_stmt->identity_type == REPLICA_IDENTITY_INDEX)
	{
		 /* 穿透 */ ;
	}
	else
		elog(ERROR, "unexpected identity type %u", fc_stmt->identity_type);

	/* 检查索引是否存在 */
	fc_indexOid = get_relname_relid(fc_stmt->name, fc_rel->rd_rel->relnamespace);
	if (!OidIsValid(fc_indexOid))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("index \"%s\" for table \"%s\" does not exist",
						fc_stmt->name, RelationGetRelationName(fc_rel))));

	fc_indexRel = index_open(fc_indexOid, ShareLock);

	/* 检查索引是否在我们要更改的关系上。 */
	if (fc_indexRel->rd_index == NULL ||
		fc_indexRel->rd_index->indrelid != RelationGetRelid(fc_rel))
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("\"%s\" is not an index for table \"%s\"",
						RelationGetRelationName(fc_indexRel),
						RelationGetRelationName(fc_rel))));
	/* AM 必须支持唯一性，并且索引实际上必须是唯一的。 */
	if (!fc_indexRel->rd_indam->amcanunique ||
		!fc_indexRel->rd_index->indisunique)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("cannot use non-unique index \"%s\" as replica identity",
						RelationGetRelationName(fc_indexRel))));
	/* 延迟索引不保证始终唯一。 */
	if (!fc_indexRel->rd_index->indimmediate)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot use non-immediate index \"%s\" as replica identity",
						RelationGetRelationName(fc_indexRel))));
	/* 表达式索引不被支持。 */
	if (RelationGetIndexExpressions(fc_indexRel) != NIL)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot use expression index \"%s\" as replica identity",
						RelationGetRelationName(fc_indexRel))));
	/* 谓词索引不被支持。 */
	if (RelationGetIndexPredicate(fc_indexRel) != NIL)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot use partial index \"%s\" as replica identity",
						RelationGetRelationName(fc_indexRel))));

	/* 检查可为空列的索引。 */
	for (fc_key = 0; fc_key < IndexRelationGetNumberOfKeyAttributes(fc_indexRel); fc_key++)
	{
		int16		fc_attno = fc_indexRel->rd_index->indkey.values[fc_key];
		Form_pg_attribute fc_attr;

		/*
		 * 拒绝任何其他系统列。 (往前看，我们将不允许
		 * 在第一位包含此类列的索引，但它们可能
		 * 存在于旧的分支中。)
		 */
		if (fc_attno <= 0)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
					 errmsg("index \"%s\" cannot be used as replica identity because column %d is a system column",
							RelationGetRelationName(fc_indexRel), fc_attno)));

		fc_attr = TupleDescAttr(fc_rel->rd_att, fc_attno - 1);
		if (!fc_attr->attnotnull)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("index \"%s\" cannot be used as replica identity because column \"%s\" is nullable",
							RelationGetRelationName(fc_indexRel),
							NameStr(fc_attr->attname))));
	}

	/* 此索引适用于作为副本标识。 标记它。 */
	fc_relation_mark_replica_identity(fc_rel, fc_stmt->identity_type, fc_indexOid, true);

	index_close(fc_indexRel, NoLock);
}

/*
 * ALTER TABLE 启用/禁用行级安全性
 */
static void fc_ATExecSetRowSecurity(Relation fc_rel, bool fc_rls)
{
	Relation	fc_pg_class;
	Oid			fc_relid;
	HeapTuple	fc_tuple;

	fc_relid = RelationGetRelid(fc_rel);

	/* 拉取此关系的记录并更新它 */
	fc_pg_class = table_open(RelationRelationId, RowExclusiveLock);

	fc_tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(fc_relid));

	if (!HeapTupleIsValid(fc_tuple))
		elog(ERROR, "cache lookup failed for relation %u", fc_relid);

	((Form_pg_class) GETSTRUCT(fc_tuple))->relrowsecurity = fc_rls;
	CatalogTupleUpdate(fc_pg_class, &fc_tuple->t_self, fc_tuple);

	table_close(fc_pg_class, RowExclusiveLock);
	heap_freetuple(fc_tuple);
}

/*
 * ALTER TABLE 强制/非强制行级安全性
 */
static void fc_ATExecForceNoForceRowSecurity(Relation fc_rel, bool fc_force_rls)
{
	Relation	fc_pg_class;
	Oid			fc_relid;
	HeapTuple	fc_tuple;

	fc_relid = RelationGetRelid(fc_rel);

	fc_pg_class = table_open(RelationRelationId, RowExclusiveLock);

	fc_tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(fc_relid));

	if (!HeapTupleIsValid(fc_tuple))
		elog(ERROR, "cache lookup failed for relation %u", fc_relid);

	((Form_pg_class) GETSTRUCT(fc_tuple))->relforcerowsecurity = fc_force_rls;
	CatalogTupleUpdate(fc_pg_class, &fc_tuple->t_self, fc_tuple);

	table_close(fc_pg_class, RowExclusiveLock);
	heap_freetuple(fc_tuple);
}

/*
 * ALTER FOREIGN TABLE <name> 选项 (...)
 */
static void fc_ATExecGenericOptions(Relation fc_rel, List *fc_options)
{
	Relation	fc_ftrel;
	ForeignServer *fc_server;
	ForeignDataWrapper *fc_fdw;
	HeapTuple	fc_tuple;
	bool		fc_isnull;
	Datum		fc_repl_val[Natts_pg_foreign_table];
	bool		fc_repl_null[Natts_pg_foreign_table];
	bool		fc_repl_repl[Natts_pg_foreign_table];
	Datum		fc_datum;
	Form_pg_foreign_table fc_tableform;

	if (fc_options == NIL)
		return;

	fc_ftrel = table_open(ForeignTableRelationId, RowExclusiveLock);

	fc_tuple = SearchSysCacheCopy1(FOREIGNTABLEREL, fc_rel->rd_id);
	if (!HeapTupleIsValid(fc_tuple))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("foreign table \"%s\" does not exist",
						RelationGetRelationName(fc_rel))));
	fc_tableform = (Form_pg_foreign_table) GETSTRUCT(fc_tuple);
	fc_server = GetForeignServer(fc_tableform->ftserver);
	fc_fdw = GetForeignDataWrapper(fc_server->fdwid);

	memset(fc_repl_val, 0, sizeof(fc_repl_val));
	memset(fc_repl_null, false, sizeof(fc_repl_null));
	memset(fc_repl_repl, false, sizeof(fc_repl_repl));

	/* 提取当前选项 */
	fc_datum = SysCacheGetAttr(FOREIGNTABLEREL,
							fc_tuple,
							Anum_pg_foreign_table_ftoptions,
							&fc_isnull);
	if (fc_isnull)
		fc_datum = PointerGetDatum(NULL);

	/* 转换选项 */
	fc_datum = transformGenericOptions(ForeignTableRelationId,
									fc_datum,
									fc_options,
									fc_fdw->fdwvalidator);

	if (PointerIsValid(DatumGetPointer(fc_datum)))
		fc_repl_val[Anum_pg_foreign_table_ftoptions - 1] = fc_datum;
	else
		fc_repl_null[Anum_pg_foreign_table_ftoptions - 1] = true;

	fc_repl_repl[Anum_pg_foreign_table_ftoptions - 1] = true;

	/* 一切看起来都很好 - 更新元组 */

	fc_tuple = heap_modify_tuple(fc_tuple, RelationGetDescr(fc_ftrel),
							  fc_repl_val, fc_repl_null, fc_repl_repl);

	CatalogTupleUpdate(fc_ftrel, &fc_tuple->t_self, fc_tuple);

	/*
	 * 使 relcache 无效，以便所有会话刷新任何可能依赖于旧选项的缓存计划。
	 */
	CacheInvalidateRelcache(fc_rel);

	InvokeObjectPostAlterHook(ForeignTableRelationId,
							  RelationGetRelid(fc_rel), 0);

	table_close(fc_ftrel, RowExclusiveLock);

	heap_freetuple(fc_tuple);
}

/*
 * ALTER TABLE ALTER COLUMN 设置压缩
 *
 * 返回值是修改后的列的地址
 */
static ObjectAddress fc_ATExecSetCompression(AlteredTableInfo *fc_tab,
					 Relation fc_rel,
					 const char *fc_column,
					 Node *fc_newValue,
					 LOCKMODE fc_lockmode)
{
	Relation	fc_attrel;
	HeapTuple	fc_tuple;
	Form_pg_attribute fc_atttableform;
	AttrNumber	fc_attnum;
	char	   *fc_compression;
	char		fc_cmethod;
	ObjectAddress fc_address;

	Assert(IsA(fc_newValue, String));
	fc_compression = strVal(fc_newValue);

	fc_attrel = table_open(AttributeRelationId, RowExclusiveLock);

	/* 复制缓存条目以便我们可以在下面进行修改 */
	fc_tuple = SearchSysCacheCopyAttName(RelationGetRelid(fc_rel), fc_column);
	if (!HeapTupleIsValid(fc_tuple))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_COLUMN),
				 errmsg("column \"%s\" of relation \"%s\" does not exist",
						fc_column, RelationGetRelationName(fc_rel))));

	/* 防止他们更改系统属性 */
	fc_atttableform = (Form_pg_attribute) GETSTRUCT(fc_tuple);
	fc_attnum = fc_atttableform->attnum;
	if (fc_attnum <= 0)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot alter system column \"%s\"", fc_column)));

	/*
	 * 检查列类型是否可压缩，然后获取属性
	 * 压缩方法代码
	 */
	fc_cmethod = fc_GetAttributeCompression(fc_atttableform->atttypid, fc_compression);

	/* 更新 pg_attribute 条目 */
	fc_atttableform->attcompression = fc_cmethod;
	CatalogTupleUpdate(fc_attrel, &fc_tuple->t_self, fc_tuple);

	InvokeObjectPostAlterHook(RelationRelationId,
							  RelationGetRelid(fc_rel),
							  fc_attnum);

	/*
	 * 将更改应用于索引（仅适用于简单索引列，
	 * 与 index.c ConstructTupleDescriptor() 的行为一致）。
	 */
	fc_SetIndexStorageProperties(fc_rel, fc_attrel, fc_attnum,
							  false, 0,
							  true, fc_cmethod,
							  fc_lockmode);

	heap_freetuple(fc_tuple);

	table_close(fc_attrel, RowExclusiveLock);

	/* 使更改可见 */
	CommandCounterIncrement();

	ObjectAddressSubSet(fc_address, RelationRelationId,
						RelationGetRelid(fc_rel), fc_attnum);
	return fc_address;
}


/*
 * SET LOGGED/UNLOGGED 的准备阶段
 *
 * 这验证我们并不是在尝试更改临时表。此外，
 * 检查现有的外键约束，以避免最终生成
 * 永久表引用未记录的表。
 *
 * 如果操作是无操作（在这种情况下跳过检查），则返回值为 false；否则为 true。
 */
static bool fc_ATPrepChangePersistence(Relation fc_rel, bool fc_toLogged)
{
	Relation	fc_pg_constraint;
	HeapTuple	fc_tuple;
	SysScanDesc fc_scan;
	ScanKeyData fc_skey[1];

	/*
	 * 不允许更改临时表的状态。还要验证我们是否可以
	 * 不做任何事情；在这种情况下，我们也不需要运行
	 * 下面的检查。
	 */
	switch (fc_rel->rd_rel->relpersistence)
	{
		case RELPERSISTENCE_TEMP:
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
							RelationGetRelationName(fc_rel)),
					 errtable(fc_rel)));
			break;
		case RELPERSISTENCE_PERMANENT:
			if (fc_toLogged)
				/* 无需操作 */
				return false;
			break;
		case RELPERSISTENCE_UNLOGGED:
			if (!fc_toLogged)
				/* 无需操作 */
				return false;
			break;
	}

	/*
	 * 检查在更改为 UNLOGGED 时，表是否不是任何发布的一部分，因为 UNLOGGED 表不能被发布。
	 */
	if (!fc_toLogged &&
		list_length(GetRelationPublications(RelationGetRelid(fc_rel))) > 0)
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("cannot change table \"%s\" to unlogged because it is part of a publication",
						RelationGetRelationName(fc_rel)),
				 errdetail("Unlogged relations cannot be replicated.")));

	/*
	 * 检查现有的外键约束，以保持不可变性，
	 * 永久表不能引用未记录的表。自引用
	 * 外键可以安全地忽略。
	 */
	fc_pg_constraint = table_open(ConstraintRelationId, AccessShareLock);

	/*
	 * 如果更改为永久性，则扫描 conrelid，否则为 confrelid。 这也
	 * 确定是否存在有用的索引。
	 */
	ScanKeyInit(&fc_skey[0],
				fc_toLogged ? Anum_pg_constraint_conrelid :
				Anum_pg_constraint_confrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(RelationGetRelid(fc_rel)));
	fc_scan = systable_beginscan(fc_pg_constraint,
							  fc_toLogged ? ConstraintRelidTypidNameIndexId : InvalidOid,
							  true, NULL, 1, fc_skey);

	while (HeapTupleIsValid(fc_tuple = systable_getnext(fc_scan)))
	{
		Form_pg_constraint fc_con = (Form_pg_constraint) GETSTRUCT(fc_tuple);

		if (fc_con->contype == CONSTRAINT_FOREIGN)
		{
			Oid			fc_foreignrelid;
			Relation	fc_foreignrel;

			/* 与我们用作扫描键的相反端 */
			fc_foreignrelid = fc_toLogged ? fc_con->confrelid : fc_con->conrelid;

			/* 忽略自引用 */
			if (RelationGetRelid(fc_rel) == fc_foreignrelid)
				continue;

			fc_foreignrel = relation_open(fc_foreignrelid, AccessShareLock);

			if (fc_toLogged)
			{
				if (!RelationIsPermanent(fc_foreignrel))
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
							 errmsg("could not change table \"%s\" to logged because it references unlogged table \"%s\"",
									RelationGetRelationName(fc_rel),
									RelationGetRelationName(fc_foreignrel)),
							 errtableconstraint(fc_rel, NameStr(fc_con->conname))));
			}
			else
			{
				if (RelationIsPermanent(fc_foreignrel))
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
							 errmsg("could not change table \"%s\" to unlogged because it references logged table \"%s\"",
									RelationGetRelationName(fc_rel),
									RelationGetRelationName(fc_foreignrel)),
							 errtableconstraint(fc_rel, NameStr(fc_con->conname))));
			}

			relation_close(fc_foreignrel, AccessShareLock);
		}
	}

	systable_endscan(fc_scan);

	table_close(fc_pg_constraint, AccessShareLock);

	return true;
}

/*
 * 执行 ALTER TABLE SET SCHEMA
 */
ObjectAddress AlterTableNamespace(AlterObjectSchemaStmt *fc_stmt, Oid *fc_oldschema)
{
	Relation	fc_rel;
	Oid			fc_relid;
	Oid			fc_oldNspOid;
	Oid			fc_nspOid;
	RangeVar   *fc_newrv;
	ObjectAddresses *fc_objsMoved;
	ObjectAddress fc_myself;

	fc_relid = RangeVarGetRelidExtended(fc_stmt->relation, AccessExclusiveLock,
									 fc_stmt->missing_ok ? RVR_MISSING_OK : 0,
									 fc_RangeVarCallbackForAlterRelation,
									 (void *) fc_stmt);

	if (!OidIsValid(fc_relid))
	{
		ereport(NOTICE,
				(errmsg("relation \"%s\" does not exist, skipping",
						fc_stmt->relation->relname)));
		return InvalidObjectAddress;
	}

	fc_rel = relation_open(fc_relid, NoLock);

	fc_oldNspOid = RelationGetNamespace(fc_rel);

	/* 如果是拥有的序列，禁止单独移动它。 */
	if (fc_rel->rd_rel->relkind == RELKIND_SEQUENCE)
	{
		Oid			fc_tableId;
		int32		fc_colId;

		if (sequenceIsOwned(fc_relid, DEPENDENCY_AUTO, &fc_tableId, &fc_colId) ||
			sequenceIsOwned(fc_relid, DEPENDENCY_INTERNAL, &fc_tableId, &fc_colId))
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("cannot move an owned sequence into another schema"),
					 errdetail("Sequence \"%s\" is linked to table \"%s\".",
							   RelationGetRelationName(fc_rel),
							   get_rel_name(fc_tableId))));
	}

	/* 获取并锁定模式 OID 并检查其权限。 */
	fc_newrv = makeRangeVar(fc_stmt->newschema, RelationGetRelationName(fc_rel), -1);
	fc_nspOid = RangeVarGetAndCheckCreationNamespace(fc_newrv, NoLock, NULL);

	/* 切换命名空间的常见检查 */
	CheckSetNamespace(fc_oldNspOid, fc_nspOid);

	fc_objsMoved = new_object_addresses();
	AlterTableNamespaceInternal(fc_rel, fc_oldNspOid, fc_nspOid, fc_objsMoved);
	free_object_addresses(fc_objsMoved);

	ObjectAddressSet(fc_myself, RelationRelationId, fc_relid);

	if (fc_oldschema)
		*fc_oldschema = fc_oldNspOid;

	/* 关闭关系，但在提交之前保持锁定 */
	relation_close(fc_rel, NoLock);

	return fc_myself;
}

/*
 * 迁移表或物化视图到另一个命名空间的具体过程：
 * 除了移动关系本身，它的依赖对象也会被迁移到
 * 新的模式中。
 */
void AlterTableNamespaceInternal(Relation fc_rel, Oid fc_oldNspOid, Oid fc_nspOid,
							ObjectAddresses *fc_objsMoved)
{
	Relation	fc_classRel;

	Assert(fc_objsMoved != NULL);

	/* 好的，修改pg_class行和pg_depend条目 */
	fc_classRel = table_open(RelationRelationId, RowExclusiveLock);

	AlterRelationNamespaceInternal(fc_classRel, RelationGetRelid(fc_rel), fc_oldNspOid,
								   fc_nspOid, true, fc_objsMoved);

	/* 如果表具有行类型，也修复它 */
	if (OidIsValid(fc_rel->rd_rel->reltype))
		AlterTypeNamespaceInternal(fc_rel->rd_rel->reltype,
								   fc_nspOid, false, false, fc_objsMoved);

	/* 修复其他依赖的内容 */
	fc_AlterIndexNamespaces(fc_classRel, fc_rel, fc_oldNspOid, fc_nspOid, fc_objsMoved);
	fc_AlterSeqNamespaces(fc_classRel, fc_rel, fc_oldNspOid, fc_nspOid,
					   fc_objsMoved, AccessExclusiveLock);
	AlterConstraintNamespaces(RelationGetRelid(fc_rel), fc_oldNspOid, fc_nspOid,
							  false, fc_objsMoved);

	table_close(fc_classRel, RowExclusiveLock);
}

/*
 * 迁移关系到另一个命名空间的具体过程：修复pg_class
 * 条目，以及pg_depend条目（如果有的话）。调用者必须已经
 * 打开并写锁定pg_class。
 */
void AlterRelationNamespaceInternal(Relation fc_classRel, Oid fc_relOid,
							   Oid fc_oldNspOid, Oid fc_newNspOid,
							   bool fc_hasDependEntry,
							   ObjectAddresses *fc_objsMoved)
{
	HeapTuple	fc_classTup;
	Form_pg_class fc_classForm;
	ObjectAddress fc_thisobj;
	bool		fc_already_done = false;

	/* 对于relkind=c没有关系锁，所以使用LOCKTAG_TUPLE */
	fc_classTup = SearchSysCacheLockedCopy1(RELOID, ObjectIdGetDatum(fc_relOid));
	if (!HeapTupleIsValid(fc_classTup))
		elog(ERROR, "cache lookup failed for relation %u", fc_relOid);
	fc_classForm = (Form_pg_class) GETSTRUCT(fc_classTup);

	Assert(fc_classForm->relnamespace == fc_oldNspOid);

	fc_thisobj.classId = RelationRelationId;
	fc_thisobj.objectId = fc_relOid;
	fc_thisobj.objectSubId = 0;

	/*
	 * 如果对象已经被移动，则不要再次移动。如果它
	 * 已经在正确的位置，就不要移动，但仍然触发对象
	 * 访问钩子。
	 */
	fc_already_done = object_address_present(&fc_thisobj, fc_objsMoved);
	if (!fc_already_done && fc_oldNspOid != fc_newNspOid)
	{
		ItemPointerData fc_otid = fc_classTup->t_self;

		/* 检查重复的名称（比唯一索引失败更友好） */
		if (get_relname_relid(NameStr(fc_classForm->relname),
							  fc_newNspOid) != InvalidOid)
			ereport(ERROR,
					(errcode(ERRCODE_DUPLICATE_TABLE),
					 errmsg("relation \"%s\" already exists in schema \"%s\"",
							NameStr(fc_classForm->relname),
							get_namespace_name(fc_newNspOid))));

		/* classTup是一个副本，所以可以随意修改 */
		fc_classForm->relnamespace = fc_newNspOid;

		CatalogTupleUpdate(fc_classRel, &fc_otid, fc_classTup);
		UnlockTuple(fc_classRel, &fc_otid, InplaceUpdateTupleLock);


		/* 如果调用者要求，更新对模式的依赖 */
		if (fc_hasDependEntry &&
			changeDependencyFor(RelationRelationId,
								fc_relOid,
								NamespaceRelationId,
								fc_oldNspOid,
								fc_newNspOid) != 1)
			elog(ERROR, "failed to change schema dependency for relation \"%s\"",
				 NameStr(fc_classForm->relname));
	}
	else
		UnlockTuple(fc_classRel, &fc_classTup->t_self, InplaceUpdateTupleLock);
	if (!fc_already_done)
	{
		add_exact_object_address(&fc_thisobj, fc_objsMoved);

		InvokeObjectPostAlterHook(RelationRelationId, fc_relOid, 0);
	}

	heap_freetuple(fc_classTup);
}

/*
 * 将指定关系的所有索引移到另一个命名空间。
 *
 * 注意：我们假设调用者已经进行了充分的权限检查，
 * 并且调用者对拥有的关系有合适的锁。
 */
static void fc_AlterIndexNamespaces(Relation fc_classRel, Relation fc_rel,
					 Oid fc_oldNspOid, Oid fc_newNspOid, ObjectAddresses *fc_objsMoved)
{
	List	   *fc_indexList;
	ListCell   *fc_l;

	fc_indexList = RelationGetIndexList(fc_rel);

	foreach(fc_l, fc_indexList)
	{
		Oid			fc_indexOid = lfirst_oid(fc_l);
		ObjectAddress fc_thisobj;

		fc_thisobj.classId = RelationRelationId;
		fc_thisobj.objectId = fc_indexOid;
		fc_thisobj.objectSubId = 0;

		/*
		 * 注意：目前，索引不会对命名空间有自己的依赖，
		 * 所以我们不需要执行changeDependencyFor()。
		 * pg_type中也没有行类型。
		 *
		 * XXX 这个objsMoved测试可能是多余的——我们应该有
		 * 从一个关系到每个索引的单一依赖链接？
		 */
		if (!object_address_present(&fc_thisobj, fc_objsMoved))
		{
			AlterRelationNamespaceInternal(fc_classRel, fc_indexOid,
										   fc_oldNspOid, fc_newNspOid,
										   false, fc_objsMoved);
			add_exact_object_address(&fc_thisobj, fc_objsMoved);
		}
	}

	list_free(fc_indexList);
}

/*
 * 将指定关系的所有标识和SERIAL列序列移到另一个
 * 命名空间。
 *
 * 注意：我们假设调用者已经进行了充分的权限检查，
 * 并且调用者对拥有的关系有合适的锁。
 */
static void fc_AlterSeqNamespaces(Relation fc_classRel, Relation fc_rel,
				   Oid fc_oldNspOid, Oid fc_newNspOid, ObjectAddresses *fc_objsMoved,
				   LOCKMODE fc_lockmode)
{
	Relation	fc_depRel;
	SysScanDesc fc_scan;
	ScanKeyData fc_key[2];
	HeapTuple	fc_tup;

	/*
	 * SERIAL序列是那些对表的某一列具有自动依赖关系的序列（我们不关心*哪一*列，确切来说）。
	 */
	fc_depRel = table_open(DependRelationId, AccessShareLock);

	ScanKeyInit(&fc_key[0],
				Anum_pg_depend_refclassid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(RelationRelationId));
	ScanKeyInit(&fc_key[1],
				Anum_pg_depend_refobjid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(RelationGetRelid(fc_rel)));
	/* 我们不指定refobjsubid */

	fc_scan = systable_beginscan(fc_depRel, DependReferenceIndexId, true,
							  NULL, 2, fc_key);

	while (HeapTupleIsValid(fc_tup = systable_getnext(fc_scan)))
	{
		Form_pg_depend fc_depForm = (Form_pg_depend) GETSTRUCT(fc_tup);
		Relation	fc_seqRel;

		/* 跳过除对列的自动依赖关系之外的依赖关系 */
		if (fc_depForm->refobjsubid == 0 ||
			fc_depForm->classid != RelationRelationId ||
			fc_depForm->objsubid != 0 ||
			!(fc_depForm->deptype == DEPENDENCY_AUTO || fc_depForm->deptype == DEPENDENCY_INTERNAL))
			continue;

		/* 如果是索引，则使用relation_open以防万一 */
		fc_seqRel = relation_open(fc_depForm->objid, fc_lockmode);

		/* 跳过非序列关系 */
		if (RelationGetForm(fc_seqRel)->relkind != RELKIND_SEQUENCE)
		{
			/* 无需保持锁定 */
			relation_close(fc_seqRel, fc_lockmode);
			continue;
		}

		/* 修复pg_class和pg_depend条目 */
		AlterRelationNamespaceInternal(fc_classRel, fc_depForm->objid,
									   fc_oldNspOid, fc_newNspOid,
									   true, fc_objsMoved);

		/*
		 * 序列曾在pg_type中有条目，但现在没有了。
		 * 如果我们重新恢复这一点，我们还需要将pg_type条目
		 * 移至新的命名空间（使用AlterTypeNamespaceInternal）。
		 */
		Assert(RelationGetForm(fc_seqRel)->reltype == InvalidOid);

		/* 现在我们可以关闭它。直到事务结束保持锁定。 */
		relation_close(fc_seqRel, NoLock);
	}

	systable_endscan(fc_scan);

	relation_close(fc_depRel, AccessShareLock);
}


/*
 * 这段代码支持
 *	CREATE TEMP TABLE ... ON COMMIT { DROP | PRESERVE ROWS | DELETE ROWS }
 *
 * 由于我们仅支持临时表，因此在后端本地数据结构中记住
 * 状态就足够了。
 */

/*
 * 注册新创建关系的ON COMMIT操作。
 */
void register_on_commit_action(Oid fc_relid, OnCommitAction fc_action)
{
	OnCommitItem *fc_oc;
	MemoryContext fc_oldcxt;

	/*
	 * 除非有需要采取的ON COMMIT操作，否则我们无需费心
	 * 注册关系。
	 */
	if (fc_action == ONCOMMIT_NOOP || fc_action == ONCOMMIT_PRESERVE_ROWS)
		return;

	fc_oldcxt = MemoryContextSwitchTo(CacheMemoryContext);

	fc_oc = (OnCommitItem *) palloc(sizeof(OnCommitItem));
	fc_oc->relid = fc_relid;
	fc_oc->oncommit = fc_action;
	fc_oc->creating_subid = GetCurrentSubTransactionId();
	fc_oc->deleting_subid = InvalidSubTransactionId;

	/*
	 * 我们在这里使用 lcons()，以便按照注册的逆序处理 ON COMMIT 操作。那可能不是必需的，但似乎是合理的。
	 */
	on_commits = lcons(fc_oc, on_commits);

	MemoryContextSwitchTo(fc_oldcxt);
}

/*
 * 当关系被删除时，注销任何 ON COMMIT 操作。
 *
 * 实际上，我们只是在提交后将 OnCommitItem 条目标记为待删除。
 */
void remove_on_commit_action(Oid fc_relid)
{
	ListCell   *fc_l;

	foreach(fc_l, on_commits)
	{
		OnCommitItem *fc_oc = (OnCommitItem *) lfirst(fc_l);

		if (fc_oc->relid == fc_relid)
		{
			fc_oc->deleting_subid = GetCurrentSubTransactionId();
			break;
		}
	}
}

/*
 * 执行 ON COMMIT 操作。
 *
 * 这在实际提交之前被调用，因为可能会遇到错误。
 */
void PreCommit_on_commit_actions(void)
{
	ListCell   *fc_l;
	List	   *fc_oids_to_truncate = NIL;
	List	   *fc_oids_to_drop = NIL;

	foreach(fc_l, on_commits)
	{
		OnCommitItem *fc_oc = (OnCommitItem *) lfirst(fc_l);

		/* 如果在此事务中已经被删除，则忽略条目 */
		if (fc_oc->deleting_subid != InvalidSubTransactionId)
			continue;

		switch (fc_oc->oncommit)
		{
			case ONCOMMIT_NOOP:
			case ONCOMMIT_PRESERVE_ROWS:
				/* 什么都不做（实际上不应有此类条目） */
				break;
			case ONCOMMIT_DELETE_ROWS:

				/*
				 * 如果此事务没有访问任何临时关系，
				 * 我们可以跳过截断 ON COMMIT DELETE ROWS
				 * 表，因为它们必须仍然是空的。
				 */
				if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPNAMESPACE))
					fc_oids_to_truncate = lappend_oid(fc_oids_to_truncate, fc_oc->relid);
				break;
			case ONCOMMIT_DROP:
				fc_oids_to_drop = lappend_oid(fc_oids_to_drop, fc_oc->relid);
				break;
		}
	}

	/*
	 * 在删除之前截断关系，以便在处理它们后删除关系之间的所有依赖关系。
	 * 这样做可能是浪费，因为截断的关系可能会由于其父关系被删除而被删除，但这使代码更稳健，
	 * 因为在截断时不必重新检查关系是否存在。
	 */
	if (fc_oids_to_truncate != NIL)
		heap_truncate(fc_oids_to_truncate);

	if (fc_oids_to_drop != NIL)
	{
		ObjectAddresses *fc_targetObjects = new_object_addresses();
		ListCell   *fc_l;

		foreach(fc_l, fc_oids_to_drop)
		{
			ObjectAddress fc_object;

			fc_object.classId = RelationRelationId;
			fc_object.objectId = lfirst_oid(fc_l);
			fc_object.objectSubId = 0;

			Assert(!object_address_present(&fc_object, fc_targetObjects));

			add_exact_object_address(&fc_object, fc_targetObjects);
		}

		/*
		 * 对象删除可能涉及访问 toast 表（以清理
		 * toasted 目录条目），因此确保我们有一个有效的快照。
		 */
		PushActiveSnapshot(GetTransactionSnapshot());

		/*
		 * 由于这是一个自动删除，而不是直接由用户发起的，
		 * 我们传递 PERFORM_DELETION_INTERNAL 标志。
		 */
		performMultipleDeletions(fc_targetObjects, DROP_CASCADE,
								 PERFORM_DELETION_INTERNAL | PERFORM_DELETION_QUIETLY);

		PopActiveSnapshot();

#ifdef USE_ASSERT_CHECKING

		/*
		 * 请注意，表删除将调用 remove_on_commit_action，因此
		 * 条目应标记为已删除。
		 */
		foreach(fc_l, on_commits)
		{
			OnCommitItem *fc_oc = (OnCommitItem *) lfirst(fc_l);

			if (fc_oc->oncommit != ONCOMMIT_DROP)
				continue;

			Assert(fc_oc->deleting_subid != InvalidSubTransactionId);
		}
#endif
	}
}

/*
 * ON COMMIT 管理的提交后或中止后的清理。
 *
 * 我们在这里所做的就是移除不再需要的 OnCommitItem 条目。
 *
 * 在提交期间，移除在此事务中被删除的条目；
 * 在中止期间，移除在此事务中创建的条目。
 */
void AtEOXact_on_commit_actions(bool fc_isCommit)
{
	ListCell   *fc_cur_item;

	foreach(fc_cur_item, on_commits)
	{
		OnCommitItem *fc_oc = (OnCommitItem *) lfirst(fc_cur_item);

		if (fc_isCommit ? fc_oc->deleting_subid != InvalidSubTransactionId :
			fc_oc->creating_subid != InvalidSubTransactionId)
		{
			/* cur_item 必须被移除 */
			on_commits = foreach_delete_current(on_commits, fc_cur_item);
			pfree(fc_oc);
		}
		else
		{
			/* cur_item 必须被保留 */
			fc_oc->creating_subid = InvalidSubTransactionId;
			fc_oc->deleting_subid = InvalidSubTransactionId;
		}
	}
}

/*
 * ON COMMIT 管理的子提交后或子中止后的清理。
 *
 * 在子中止期间，我们可以立即移除在此子事务中创建的条目。
 * 在子提交期间，只需将此子事务中标记的条目重新标记为父事务的责任。
 */
void AtEOSubXact_on_commit_actions(bool fc_isCommit, SubTransactionId fc_mySubid,
							  SubTransactionId fc_parentSubid)
{
	ListCell   *fc_cur_item;

	foreach(fc_cur_item, on_commits)
	{
		OnCommitItem *fc_oc = (OnCommitItem *) lfirst(fc_cur_item);

		if (!fc_isCommit && fc_oc->creating_subid == fc_mySubid)
		{
			/* cur_item 必须被移除 */
			on_commits = foreach_delete_current(on_commits, fc_cur_item);
			pfree(fc_oc);
		}
		else
		{
			/* cur_item 必须被保留 */
			if (fc_oc->creating_subid == fc_mySubid)
				fc_oc->creating_subid = fc_parentSubid;
			if (fc_oc->deleting_subid == fc_mySubid)
				fc_oc->deleting_subid = fc_isCommit ? fc_parentSubid : InvalidSubTransactionId;
		}
	}
}

/*
 * 这是用于 RangeVarGetRelidExtended() 的回调。
 * 仅在（1）它是一个普通或分区表、物化视图或 TOAST 表，以及
 * （2）当前用户是所有者（或超级用户）时，允许锁定关系。
 * 这满足 CLUSTER、REINDEX TABLE 和 REFRESH MATERIALIZED VIEW 的权限检查需求；
 * 我们在这里公开它，以便所有人都可以使用。
 */
void RangeVarCallbackOwnsTable(const RangeVar *fc_relation,
						  Oid fc_relId, Oid fc_oldRelId, void *fc_arg)
{
	char		fc_relkind;

	/* 如果未找到关系，则不需要做任何事情。 */
	if (!OidIsValid(fc_relId))
		return;

	/*
	 * 如果关系确实存在，检查它是否是一个索引。但请注意，
	 * 在我们进行名称查找和现在之间，关系可能已经被删除。在这种情况下，没什么可做的。
	 */
	fc_relkind = get_rel_relkind(fc_relId);
	if (!fc_relkind)
		return;
	if (fc_relkind != RELKIND_RELATION && fc_relkind != RELKIND_TOASTVALUE &&
		fc_relkind != RELKIND_MATVIEW && fc_relkind != RELKIND_PARTITIONED_TABLE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("\"%s\" is not a table or materialized view", fc_relation->relname)));

	/* 检查权限 */
	if (!pg_class_ownercheck(fc_relId, GetUserId()))
		aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(fc_relId)), fc_relation->relname);
}

/*
 * RangeVarGetRelidExtended() 的回调，用于 TRUNCATE 处理。
 */
static void fc_RangeVarCallbackForTruncate(const RangeVar *fc_relation,
							Oid fc_relId, Oid fc_oldRelId, void *fc_arg)
{
	HeapTuple	fc_tuple;

	/* 如果未找到关系，则不需要做任何事情。 */
	if (!OidIsValid(fc_relId))
		return;

	fc_tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(fc_relId));
	if (!HeapTupleIsValid(fc_tuple))	/* 不应该发生 */
		elog(ERROR, "cache lookup failed for relation %u", fc_relId);

	fc_truncate_check_rel(fc_relId, (Form_pg_class) GETSTRUCT(fc_tuple));
	fc_truncate_check_perms(fc_relId, (Form_pg_class) GETSTRUCT(fc_tuple));

	ReleaseSysCache(fc_tuple);
}

/*
 * RangeVarGetRelidExtended() 的回调，类似于
 * RangeVarCallbackOwnsTable() 但不检查关系的类型。
 */
void RangeVarCallbackOwnsRelation(const RangeVar *fc_relation,
							 Oid fc_relId, Oid fc_oldRelId, void *fc_arg)
{
	HeapTuple	fc_tuple;

	/* 如果未找到关系，则不需要做任何事情。 */
	if (!OidIsValid(fc_relId))
		return;

	fc_tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(fc_relId));
	if (!HeapTupleIsValid(fc_tuple))	/* 不应该发生 */
		elog(ERROR, "cache lookup failed for relation %u", fc_relId);

	if (!pg_class_ownercheck(fc_relId, GetUserId()))
		aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(fc_relId)),
					   fc_relation->relname);

	if (!allowSystemTableMods &&
		IsSystemClass(fc_relId, (Form_pg_class) GETSTRUCT(fc_tuple)))
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("permission denied: \"%s\" is a system catalog",
						fc_relation->relname)));

	ReleaseSysCache(fc_tuple);
}

/*
 * 重命名、设置模式和修改表处理的通用 RangeVarGetRelid 回调。
 */
static void fc_RangeVarCallbackForAlterRelation(const RangeVar *fc_rv, Oid fc_relid, Oid fc_oldrelid,
								 void *fc_arg)
{
	Node	   *fc_stmt = (Node *) fc_arg;
	ObjectType	fc_reltype;
	HeapTuple	fc_tuple;
	Form_pg_class fc_classform;
	AclResult	fc_aclresult;
	char		fc_relkind;

	fc_tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(fc_relid));
	if (!HeapTupleIsValid(fc_tuple))
		return;					/* 并发删除 */
	fc_classform = (Form_pg_class) GETSTRUCT(fc_tuple);
	fc_relkind = fc_classform->relkind;

	/* 必须拥有关系。 */
	if (!pg_class_ownercheck(fc_relid, GetUserId()))
		aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(fc_relid)), fc_rv->relname);

	/* 除非明确允许，否则不得修改系统表。 */
	if (!allowSystemTableMods && IsSystemClass(fc_relid, fc_classform))
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("permission denied: \"%s\" is a system catalog",
						fc_rv->relname)));

	/*
	 * 从语句解析树中提取指定的关系类型。
	 *
	 * 此外，对于 ALTER .. RENAME，检查权限：用户必须（仍然）
	 * 对包含命名空间具有 CREATE 权限。
	 */
	if (IsA(fc_stmt, RenameStmt))
	{
		fc_aclresult = pg_namespace_aclcheck(fc_classform->relnamespace,
										  GetUserId(), ACL_CREATE);
		if (fc_aclresult != ACLCHECK_OK)
			aclcheck_error(fc_aclresult, OBJECT_SCHEMA,
						   get_namespace_name(fc_classform->relnamespace));
		fc_reltype = ((RenameStmt *) fc_stmt)->renameType;
	}
	else if (IsA(fc_stmt, AlterObjectSchemaStmt))
		fc_reltype = ((AlterObjectSchemaStmt *) fc_stmt)->objectType;

	else if (IsA(fc_stmt, AlterTableStmt))
		fc_reltype = ((AlterTableStmt *) fc_stmt)->objtype;
	else
	{
		elog(ERROR, "unrecognized node type: %d", (int) nodeTag(fc_stmt));
		fc_reltype = OBJECT_TABLE; /* 安抚编译器 */
	}

	/*
	 * 为了与早期版本兼容，我们允许在大多数其他类型的关系上使用
	 * ALTER TABLE（但不包括复合类型）。在 RENAME 的情况下，我们允许
	 * ALTER INDEX 具有类似的灵活性，但其他情况下则不允许。否则，用户必须为
	 * 涉及的关系选择正确的命令形式。
	 */
	if (fc_reltype == OBJECT_SEQUENCE && fc_relkind != RELKIND_SEQUENCE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("\"%s\" is not a sequence", fc_rv->relname)));

	if (fc_reltype == OBJECT_VIEW && fc_relkind != RELKIND_VIEW)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("\"%s\" is not a view", fc_rv->relname)));

	if (fc_reltype == OBJECT_MATVIEW && fc_relkind != RELKIND_MATVIEW)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("\"%s\" is not a materialized view", fc_rv->relname)));

	if (fc_reltype == OBJECT_FOREIGN_TABLE && fc_relkind != RELKIND_FOREIGN_TABLE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("\"%s\" is not a foreign table", fc_rv->relname)));

	if (fc_reltype == OBJECT_TYPE && fc_relkind != RELKIND_COMPOSITE_TYPE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("\"%s\" is not a composite type", fc_rv->relname)));

	if (fc_reltype == OBJECT_INDEX && fc_relkind != RELKIND_INDEX &&
		fc_relkind != RELKIND_PARTITIONED_INDEX
		&& !IsA(fc_stmt, RenameStmt))
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("\"%s\" is not an index", fc_rv->relname)));

	/*
	 * 不允许在复合类型上使用 ALTER TABLE。我们希望人们为此使用 ALTER
	 * TYPE。
	 */
	if (fc_reltype != OBJECT_TYPE && fc_relkind == RELKIND_COMPOSITE_TYPE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("\"%s\" is a composite type", fc_rv->relname),
				 errhint("Use ALTER TYPE instead.")));

	/*
	 * 不允许在无法移动到不同模式的关系上使用 ALTER TABLE .. SET SCHEMA，
	 * 例如索引和 TOAST 表。
	 */
	if (IsA(fc_stmt, AlterObjectSchemaStmt))
	{
		if (fc_relkind == RELKIND_INDEX || fc_relkind == RELKIND_PARTITIONED_INDEX)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("cannot change schema of index \"%s\"",
							fc_rv->relname),
					 errhint("Change the schema of the table instead.")));
		else if (fc_relkind == RELKIND_COMPOSITE_TYPE)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("cannot change schema of composite type \"%s\"",
							fc_rv->relname),
					 errhint("Use ALTER TYPE instead.")));
		else if (fc_relkind == RELKIND_TOASTVALUE)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("cannot change schema of TOAST table \"%s\"",
							fc_rv->relname),
					 errhint("Change the schema of the table instead.")));
	}

	ReleaseSysCache(fc_tuple);
}

/*
 * 转换分区键中存在的任何表达式
 *
 * 返回一个转换后的 PartitionSpec，以及策略代码
 */
static PartitionSpec *
fc_transformPartitionSpec(Relation fc_rel, PartitionSpec *fc_partspec, char *fc_strategy)
{
	PartitionSpec *fc_newspec;
	ParseState *fc_pstate;
	ParseNamespaceItem *fc_nsitem;
	ListCell   *fc_l;

	fc_newspec = makeNode(PartitionSpec);

	fc_newspec->strategy = fc_partspec->strategy;
	fc_newspec->partParams = NIL;
	fc_newspec->location = fc_partspec->location;

	/* 解析分区策略名称 */
	if (pg_strcasecmp(fc_partspec->strategy, "hash") == 0)
		*fc_strategy = PARTITION_STRATEGY_HASH;
	else if (pg_strcasecmp(fc_partspec->strategy, "list") == 0)
		*fc_strategy = PARTITION_STRATEGY_LIST;
	else if (pg_strcasecmp(fc_partspec->strategy, "range") == 0)
		*fc_strategy = PARTITION_STRATEGY_RANGE;
	else
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("unrecognized partitioning strategy \"%s\"",
						fc_partspec->strategy)));

	/* 检查策略的有效列数 */
	if (*fc_strategy == PARTITION_STRATEGY_LIST &&
		list_length(fc_partspec->partParams) != 1)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("cannot use \"list\" partition strategy with more than one column")));

	/*
	 * 创建一个虚拟的 ParseState，并将目标关系插入为其唯一的
	 * rangetable 项。我们需要一个 ParseState 用于 transformExpr。
	 */
	fc_pstate = make_parsestate(NULL);
	fc_nsitem = addRangeTableEntryForRelation(fc_pstate, fc_rel, AccessShareLock,
										   NULL, false, true);
	addNSItemToQuery(fc_pstate, fc_nsitem, true, true, true);

	/* 处理任何分区表达式 */
	foreach(fc_l, fc_partspec->partParams)
	{
		PartitionElem *fc_pelem = lfirst_node(PartitionElem, fc_l);

		if (fc_pelem->expr)
		{
			/* 复制，以避免在输入上涂写 */
			fc_pelem = copyObject(fc_pelem);

			/* 现在对表达式进行解析转换 */
			fc_pelem->expr = transformExpr(fc_pstate, fc_pelem->expr,
										EXPR_KIND_PARTITION_EXPRESSION);

			/* 我们还必须修复它的排序规则 */
			assign_expr_collations(fc_pstate, fc_pelem->expr);
		}

		fc_newspec->partParams = lappend(fc_newspec->partParams, fc_pelem);
	}

	return fc_newspec;
}

/*
 * 从 PartitionElems 列表计算每个分区列的信息。
 * PartitionElems 中的表达式必须已经经过解析分析。
 */
static void fc_ComputePartitionAttrs(ParseState *fc_pstate, Relation fc_rel, List *fc_partParams, AttrNumber *fc_partattrs,
					  List **fc_partexprs, Oid *fc_partopclass, Oid *fc_partcollation,
					  char fc_strategy)
{
	int			fc_attn;
	ListCell   *fc_lc;
	Oid			fc_am_oid;

	fc_attn = 0;
	foreach(fc_lc, fc_partParams)
	{
		PartitionElem *fc_pelem = lfirst_node(PartitionElem, fc_lc);
		Oid			fc_atttype;
		Oid			fc_attcollation;

		if (fc_pelem->name != NULL)
		{
			/* 简单属性引用 */
			HeapTuple	fc_atttuple;
			Form_pg_attribute fc_attform;

			fc_atttuple = SearchSysCacheAttName(RelationGetRelid(fc_rel),
											 fc_pelem->name);
			if (!HeapTupleIsValid(fc_atttuple))
				ereport(ERROR,
						(errcode(ERRCODE_UNDEFINED_COLUMN),
						 errmsg("column \"%s\" named in partition key does not exist",
								fc_pelem->name),
						 parser_errposition(fc_pstate, fc_pelem->location)));
			fc_attform = (Form_pg_attribute) GETSTRUCT(fc_atttuple);

			if (fc_attform->attnum <= 0)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
						 errmsg("cannot use system column \"%s\" in partition key",
								fc_pelem->name),
						 parser_errposition(fc_pstate, fc_pelem->location)));

			/*
			 * 生成的列无法工作：它们在 BEFORE
			 * 触发器之后计算，但分区路由在所有触发器之前完成。
			 */
			if (fc_attform->attgenerated)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
						 errmsg("cannot use generated column in partition key"),
						 errdetail("Column \"%s\" is a generated column.",
								   fc_pelem->name),
						 parser_errposition(fc_pstate, fc_pelem->location)));

			fc_partattrs[fc_attn] = fc_attform->attnum;
			fc_atttype = fc_attform->atttypid;
			fc_attcollation = fc_attform->attcollation;
			ReleaseSysCache(fc_atttuple);
		}
		else
		{
			/* 表达式 */
			Node	   *fc_expr = fc_pelem->expr;
			char		fc_partattname[16];

			Assert(fc_expr != NULL);
			fc_atttype = exprType(fc_expr);
			fc_attcollation = exprCollation(fc_expr);

			/*
			 * 表达式必须是可存储的类型（例如，不能是 RECORD）。
			 * 测试与表列是否是安全类型的测试相同（这就是为什么我们不需要
			 * 检查非表达式的情况）。
			 */
			snprintf(fc_partattname, sizeof(fc_partattname), "%d", fc_attn + 1);
			CheckAttributeType(fc_partattname,
							   fc_atttype, fc_attcollation,
							   NIL, CHKATYPE_IS_PARTKEY);

			/*
			 * 删除任何顶层的 COLLATE 子句。这确保我们将
			 * "x COLLATE y" 和 "(x COLLATE y)" 视为相同。
			 */
			while (IsA(fc_expr, CollateExpr))
				fc_expr = (Node *) ((CollateExpr *) fc_expr)->arg;

			if (IsA(fc_expr, Var) &&
				((Var *) fc_expr)->varattno > 0)
			{
				/*
				 * 用户写了 "(column)" 或 "(column COLLATE something)"。
				 * 无论如何都将其视为简单属性。
				 */
				fc_partattrs[fc_attn] = ((Var *) fc_expr)->varattno;
			}
			else
			{
				Bitmapset  *fc_expr_attrs = NULL;
				int			fc_i;

				fc_partattrs[fc_attn] = 0;	/* 将列标记为表达式 */
				*fc_partexprs = lappend(*fc_partexprs, fc_expr);

				/*
				 * transformPartitionSpec() 应该已经拒绝了
				 * 子查询、聚合、窗口函数和 SRFs，基于分区表达式的
				 * EXPR_KIND_。
				 */

				/*
				 * 不能允许系统列引用，因为那会使分区路由
				 * 不可能：在我们需要进行该操作时，其值尚未知晓。
				 */
				pull_varattnos(fc_expr, 1, &fc_expr_attrs);
				for (fc_i = FirstLowInvalidHeapAttributeNumber; fc_i < 0; fc_i++)
				{
					if (bms_is_member(fc_i - FirstLowInvalidHeapAttributeNumber,
									  fc_expr_attrs))
						ereport(ERROR,
								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
								 errmsg("partition key expressions cannot contain system column references")));
				}

				/*
				 * 生成的列无法工作：它们在 BEFORE 触发器之后计算，
				 * 但分区路由在所有触发器之前完成。
				 */
				fc_i = -1;
				while ((fc_i = bms_next_member(fc_expr_attrs, fc_i)) >= 0)
				{
					AttrNumber	fc_attno = fc_i + FirstLowInvalidHeapAttributeNumber;

					if (fc_attno > 0 &&
						TupleDescAttr(RelationGetDescr(fc_rel), fc_attno - 1)->attgenerated)
						ereport(ERROR,
								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
								 errmsg("cannot use generated column in partition key"),
								 errdetail("Column \"%s\" is a generated column.",
										   get_attname(RelationGetRelid(fc_rel), fc_attno, false)),
								 parser_errposition(fc_pstate, fc_pelem->location)));
				}

				/*
				 * 在检查可变性之前预处理表达式。
				 * 这对于在 contain_mutable_functions_after_planning 中
				 * 描述的原因至关重要。然而，我们为自己调用
				 * expression_planner，而不是使用该函数，因为如果
				 * 常量折叠将表达式简化为常量，我们希望知道
				 * 这一点，以便在下面进行投诉。
				 *
				 * 像 contain_mutable_functions_after_planning 一样，假设
				 * expression_planner 不会篡改其输入，因此这不会影响
				 * 我们上面保存的 partexprs 条目。
				 */
				fc_expr = (Node *) expression_planner((Expr *) fc_expr);

				/*
				 * 分区表达式不能包含可变函数，
				 * 因为在分区边界结构没有变化的情况下，
				 * 给定行必须始终映射到相同的分区。
				 */
				if (contain_mutable_functions(fc_expr))
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
							 errmsg("functions in partition key expression must be marked IMMUTABLE")));

				/*
				 * 虽然分区表达式是常量并不算 *错误*，
				 * 但拒绝这样的键似乎更好。
				 */
				if (IsA(fc_expr, Const))
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
							 errmsg("cannot use constant expression as partition key")));
			}
		}

		/*
		 * 如果有则应用排序顺序覆盖
		 */
		if (fc_pelem->collation)
			fc_attcollation = get_collation_oid(fc_pelem->collation, false);

		/*
		 * 检查我们是否有排序顺序，仅当它是可排序类型时。唯一
		 * 预期的失败是 (1) 对不可排序类型应用 COLLATE，
		 * 或 (2) 分区表达式具有未解决的排序顺序。但
		 * 我们也可以将其编码为完整的一致性检查。
		 */
		if (type_is_collatable(fc_atttype))
		{
			if (!OidIsValid(fc_attcollation))
				ereport(ERROR,
						(errcode(ERRCODE_INDETERMINATE_COLLATION),
						 errmsg("could not determine which collation to use for partition expression"),
						 errhint("Use the COLLATE clause to set the collation explicitly.")));
		}
		else
		{
			if (OidIsValid(fc_attcollation))
				ereport(ERROR,
						(errcode(ERRCODE_DATATYPE_MISMATCH),
						 errmsg("collations are not supported by type %s",
								format_type_be(fc_atttype))));
		}

		fc_partcollation[fc_attn] = fc_attcollation;

		/*
		 * 确定适当的运算符类。对于列表和范围
		 * 分区，我们使用 btree 运算符类；哈希分区使用
		 * 哈希运算符类。
		 */
		if (fc_strategy == PARTITION_STRATEGY_HASH)
			fc_am_oid = HASH_AM_OID;
		else
			fc_am_oid = BTREE_AM_OID;

		if (!fc_pelem->opclass)
		{
			fc_partopclass[fc_attn] = GetDefaultOpClass(fc_atttype, fc_am_oid);

			if (!OidIsValid(fc_partopclass[fc_attn]))
			{
				if (fc_strategy == PARTITION_STRATEGY_HASH)
					ereport(ERROR,
							(errcode(ERRCODE_UNDEFINED_OBJECT),
							 errmsg("data type %s has no default operator class for access method \"%s\"",
									format_type_be(fc_atttype), "hash"),
							 errhint("You must specify a hash operator class or define a default hash operator class for the data type.")));
				else
					ereport(ERROR,
							(errcode(ERRCODE_UNDEFINED_OBJECT),
							 errmsg("data type %s has no default operator class for access method \"%s\"",
									format_type_be(fc_atttype), "btree"),
							 errhint("You must specify a btree operator class or define a default btree operator class for the data type.")));
			}
		}
		else
			fc_partopclass[fc_attn] = ResolveOpClass(fc_pelem->opclass,
											   fc_atttype,
											   fc_am_oid == HASH_AM_OID ? "hash" : "btree",
											   fc_am_oid);

		fc_attn++;
	}
}

/*
 * PartConstraintImpliedByRelConstraint
 *		扫描关系的现有约束是否暗示分区约束？
 *
 * "现有约束" 包括其检查约束和列级
 * NOT NULL 约束。partConstraint 描述分区约束，
 * 以隐式与形式表示。
 */
bool PartConstraintImpliedByRelConstraint(Relation fc_scanrel,
									 List *fc_partConstraint)
{
	List	   *fc_existConstraint = NIL;
	TupleConstr *fc_constr = RelationGetDescr(fc_scanrel)->constr;
	int			fc_i;

	if (fc_constr && fc_constr->has_not_null)
	{
		int			fc_natts = fc_scanrel->rd_att->natts;

		for (fc_i = 1; fc_i <= fc_natts; fc_i++)
		{
			Form_pg_attribute fc_att = TupleDescAttr(fc_scanrel->rd_att, fc_i - 1);

			if (fc_att->attnotnull && !fc_att->attisdropped)
			{
				NullTest   *fc_ntest = makeNode(NullTest);

				fc_ntest->arg = (Expr *) makeVar(1,
											  fc_i,
											  fc_att->atttypid,
											  fc_att->atttypmod,
											  fc_att->attcollation,
											  0);
				fc_ntest->nulltesttype = IS_NOT_NULL;

				/*
				 * argisrow=false 在复合列上也是正确的，
				 * 因为 attnotnull 在这种情况下并不表示
				 * SQL 规范的 IS NOT NULL 测试，仅表示
				 * IS DISTINCT FROM NULL。
				 */
				fc_ntest->argisrow = false;
				fc_ntest->location = -1;
				fc_existConstraint = lappend(fc_existConstraint, fc_ntest);
			}
		}
	}

	return fc_ConstraintImpliedByRelConstraint(fc_scanrel, fc_partConstraint, fc_existConstraint);
}

/*
 * ConstraintImpliedByRelConstraint
 *		扫描关系的现有约束是否暗示给定的约束？
 *
 * testConstraint是要验证的约束。provenConstraint是一个调用者提供的条件列表，该函数可以假设这些条件为真。provenConstraint和testConstraint必须是隐式与形式，只能包含不变的子句，并且只能包含变量varno = 1。
 */
bool fc_ConstraintImpliedByRelConstraint(Relation fc_scanrel, List *fc_testConstraint, List *fc_provenConstraint)
{
	List	   *fc_existConstraint = list_copy(fc_provenConstraint);
	TupleConstr *fc_constr = RelationGetDescr(fc_scanrel)->constr;
	int			fc_num_check,
				fc_i;

	fc_num_check = (fc_constr != NULL) ? fc_constr->num_check : 0;
	for (fc_i = 0; fc_i < fc_num_check; fc_i++)
	{
		Node	   *fc_cexpr;

		/*
		 * 如果这个约束还没有完全验证，我们必须在这里忽略它。
		 */
		if (!fc_constr->check[fc_i].ccvalid)
			continue;

		fc_cexpr = stringToNode(fc_constr->check[fc_i].ccbin);

		/*
		 * 通过常量简化和标准化运行每个表达式。这是必要的，因为我们将把它与类似处理的分区约束表达式进行比较，如果没有这个，可能无法检测到有效的匹配。
		 */
		fc_cexpr = eval_const_expressions(NULL, fc_cexpr);
		fc_cexpr = (Node *) canonicalize_qual((Expr *) fc_cexpr, true);

		fc_existConstraint = list_concat(fc_existConstraint,
									  make_ands_implicit((Expr *) fc_cexpr));
	}

	/*
	 * 尝试进行证明。由于我们正在比较检查约束，我们需要使用弱暗示，即我们假设existConstraint不是假，并尝试证明testConstraint也是如此。
	 *
	 * 请注意，predicate_implied_by假设其第一个参数是已知的不变的。这对NOT NULL和分区约束来说应该始终成立，因此我们在这里不进行测试。
	 */
	return predicate_implied_by(fc_testConstraint, fc_existConstraint, true);
}

/*
 * QueuePartitionConstraintValidation
 *
 * 添加一个条目到工作队列，以便第3阶段验证给定关系的分区约束以及它的所有子节点。
 *
 * 我们首先验证给定的约束是否由先前存在的关系约束所暗示；如果是，则不需要扫描表以进行验证，因此在这种情况下不要排队。
 */
static void fc_QueuePartitionConstraintValidation(List **fc_wqueue, Relation fc_scanrel,
								   List *fc_partConstraint,
								   bool fc_validate_default)
{
	/*
	 * 基于表的现有约束，确定我们是否可以跳过扫描表。
	 */
	if (PartConstraintImpliedByRelConstraint(fc_scanrel, fc_partConstraint))
	{
		if (!fc_validate_default)
			ereport(DEBUG1,
					(errmsg_internal("partition constraint for table \"%s\" is implied by existing constraints",
									 RelationGetRelationName(fc_scanrel))));
		else
			ereport(DEBUG1,
					(errmsg_internal("updated partition constraint for default partition \"%s\" is implied by existing constraints",
									 RelationGetRelationName(fc_scanrel))));
		return;
	}

	/*
	 * 约束被证明是不充分的。对于普通关系，现在排队一个验证项；对于分区表，递归处理每个分区。
	 */
	if (fc_scanrel->rd_rel->relkind == RELKIND_RELATION)
	{
		AlteredTableInfo *fc_tab;

		/* 获取一个工作队列条目。 */
		fc_tab = fc_ATGetQueueEntry(fc_wqueue, fc_scanrel);
		Assert(fc_tab->partition_constraint == NULL);
		fc_tab->partition_constraint = (Expr *) linitial(fc_partConstraint);
		fc_tab->validate_default = fc_validate_default;
	}
	else if (fc_scanrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
	{
		PartitionDesc fc_partdesc = RelationGetPartitionDesc(fc_scanrel, true);
		int			fc_i;

		for (fc_i = 0; fc_i < fc_partdesc->nparts; fc_i++)
		{
			Relation	fc_part_rel;
			List	   *fc_thisPartConstraint;

			/*
			 * 这是我们需要的最小锁定，以防止死锁。
			 */
			fc_part_rel = table_open(fc_partdesc->oids[fc_i], AccessExclusiveLock);

			/*
			 * 调整scanrel的约束，使其与这个分区的属性编号匹配。
			 */
			fc_thisPartConstraint =
				map_partition_varattnos(fc_partConstraint, 1,
										fc_part_rel, fc_scanrel);

			fc_QueuePartitionConstraintValidation(fc_wqueue, fc_part_rel,
											   fc_thisPartConstraint,
											   fc_validate_default);
			table_close(fc_part_rel, NoLock);	/* 保持锁定直到提交 */
		}
	}
}

/*
 * ALTER TABLE <name> ATTACH PARTITION <partition-name> FOR VALUES
 *
 * 返回新附加分区的地址。
 */
static ObjectAddress fc_ATExecAttachPartition(List **fc_wqueue, Relation fc_rel, PartitionCmd *fc_cmd,
					  AlterTableUtilityContext *fc_context)
{
	Relation	fc_attachrel,
				fc_catalog;
	List	   *fc_attachrel_children;
	List	   *fc_partConstraint;
	SysScanDesc fc_scan;
	ScanKeyData fc_skey;
	AttrNumber	fc_attno;
	int			fc_natts;
	TupleDesc	fc_tupleDesc;
	ObjectAddress fc_address;
	const char *fc_trigger_name;
	Oid			fc_defaultPartOid;
	List	   *fc_partBoundConstraint;
	ParseState *fc_pstate = make_parsestate(NULL);

	fc_pstate->p_sourcetext = fc_context->queryString;

	/*
	 * 如果存在默认分区，我们必须锁定它，因为附加新分区将改变它的分区约束。
	 */
	fc_defaultPartOid =
		get_default_oid_from_partdesc(RelationGetPartitionDesc(fc_rel, true));
	if (OidIsValid(fc_defaultPartOid))
		LockRelationOid(fc_defaultPartOid, AccessExclusiveLock);

	fc_attachrel = table_openrv(fc_cmd->name, AccessExclusiveLock);

	/*
	 * XXX 我认为在这个时候也应该锁定所有被外键引用的表。
	 */

	/*
	 * 必须是父表和源表的所有者--父级在ATPrepCmd中的ATSimplePermissions调用中进行了检查。
	 */
	fc_ATSimplePermissions(AT_AttachPartition, fc_attachrel, ATT_TABLE | ATT_FOREIGN_TABLE);

	/* 一个分区只能有一个父级 */
	if (fc_attachrel->rd_rel->relispartition)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("\"%s\" is already a partition",
						RelationGetRelationName(fc_attachrel))));

	if (OidIsValid(fc_attachrel->rd_rel->reloftype))
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("cannot attach a typed table as partition")));

	/*
	 * 正在附加的表不应已经是继承的一部分；要么作为子表...
	 */
	fc_catalog = table_open(InheritsRelationId, AccessShareLock);
	ScanKeyInit(&fc_skey,
				Anum_pg_inherits_inhrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(RelationGetRelid(fc_attachrel)));
	fc_scan = systable_beginscan(fc_catalog, InheritsRelidSeqnoIndexId, true,
							  NULL, 1, &fc_skey);
	if (HeapTupleIsValid(systable_getnext(fc_scan)))
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("cannot attach inheritance child as partition")));
	systable_endscan(fc_scan);

	/* ...要么作为父表（分区的情况除外） */
	ScanKeyInit(&fc_skey,
				Anum_pg_inherits_inhparent,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(RelationGetRelid(fc_attachrel)));
	fc_scan = systable_beginscan(fc_catalog, InheritsParentIndexId, true, NULL,
							  1, &fc_skey);
	if (HeapTupleIsValid(systable_getnext(fc_scan)) &&
		fc_attachrel->rd_rel->relkind == RELKIND_RELATION)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("cannot attach inheritance parent as partition")));
	systable_endscan(fc_scan);
	table_close(fc_catalog, AccessShareLock);

	/*
	 * 通过检查 rel 是否是 attachrel 的一个分区来防止循环性。(特别是，这不允许将一个 rel 设置为它自身的分区。)
	 *
	 * 我们通过检查 rel 是否是 attachrel 的分区列表中的成员来做到这一点，前提是后者至少被分区。我们希望避免再次构建这个列表，所以我们请求对所有分区进行最强锁定。我们需要最强的锁，因为如果我们发现正在附加的表（或其叶子分区）可能包含违反分区约束的行，我们可能决定扫描它们。如果表有一个约束，可以防止这些行，而这个约束根据定义在所有分区中都存在，我们就不需要扫描该表及其分区。但我们不能冒着现在采取较弱的锁而在需要时再采取较强的锁导致死锁的风险。
	 */
	fc_attachrel_children = find_all_inheritors(RelationGetRelid(fc_attachrel),
											 AccessExclusiveLock, NULL);
	if (list_member_oid(fc_attachrel_children, RelationGetRelid(fc_rel)))
		ereport(ERROR,
				(errcode(ERRCODE_DUPLICATE_TABLE),
				 errmsg("circular inheritance not allowed"),
				 errdetail("\"%s\" is already a child of \"%s\".",
						   RelationGetRelationName(fc_rel),
						   RelationGetRelationName(fc_attachrel))));

	/* 如果父表是永久的，那么它的所有分区也必须是永久的。 */
	if (fc_rel->rd_rel->relpersistence != RELPERSISTENCE_TEMP &&
		fc_attachrel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("cannot attach a temporary relation as partition of permanent relation \"%s\"",
						RelationGetRelationName(fc_rel))));

	/* 临时父表不能有一个不是临时的分区 */
	if (fc_rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
		fc_attachrel->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("cannot attach a permanent relation as partition of temporary relation \"%s\"",
						RelationGetRelationName(fc_rel))));

	/* 如果父表是临时的，它必须属于这个会话 */
	if (fc_rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
		!fc_rel->rd_islocaltemp)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("cannot attach as partition of temporary relation of another session")));

	/* 分区同样适用 */
	if (fc_attachrel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
		!fc_attachrel->rd_islocaltemp)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("cannot attach temporary relation of another session as partition")));

	/* 检查 attachrel 中是否有任何列不在父表中 */
	fc_tupleDesc = RelationGetDescr(fc_attachrel);
	fc_natts = fc_tupleDesc->natts;
	for (fc_attno = 1; fc_attno <= fc_natts; fc_attno++)
	{
		Form_pg_attribute fc_attribute = TupleDescAttr(fc_tupleDesc, fc_attno - 1);
		char	   *fc_attributeName = NameStr(fc_attribute->attname);

		/* 忽略已删除的 */
		if (fc_attribute->attisdropped)
			continue;

		/* 尝试在父表中找到该列（按列名匹配） */
		if (!SearchSysCacheExists2(ATTNAME,
								   ObjectIdGetDatum(RelationGetRelid(fc_rel)),
								   CStringGetDatum(fc_attributeName)))
			ereport(ERROR,
					(errcode(ERRCODE_DATATYPE_MISMATCH),
					 errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"",
							RelationGetRelationName(fc_attachrel), fc_attributeName,
							RelationGetRelationName(fc_rel)),
					 errdetail("The new partition may contain only the columns present in parent.")));
	}

	/*
	 * 如果 child_rel 有带有过渡表的行级触发器，我们
	 * 当前不允许它成为一个分区。另见 ATExecAddInherit() 和 CreateTrigger() 中的禁令。
	 */
	fc_trigger_name = FindTriggerIncompatibleWithInheritance(fc_attachrel->trigdesc);
	if (fc_trigger_name != NULL)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("trigger \"%s\" prevents table \"%s\" from becoming a partition",
						fc_trigger_name, RelationGetRelationName(fc_attachrel)),
				 errdetail("ROW triggers with transition tables are not supported on partitions.")));

	/*
	 * 检查新分区的约束是否有效且不与父表的现有分区重叠 - 注意它不会在错误时返回。
	 */
	check_new_partition_bound(RelationGetRelationName(fc_attachrel), fc_rel,
							  fc_cmd->bound, fc_pstate);

	/* 创建继承是可以的。其余的检查在那里执行 */
	fc_CreateInheritance(fc_attachrel, fc_rel);

	/* 更新pg_class条目。 */
	StorePartitionBound(fc_attachrel, fc_rel, fc_cmd->bound);

	/* 确保分区中存在正确的一组索引。 */
	fc_AttachPartitionEnsureIndexes(fc_rel, fc_attachrel);

	/* 和触发器 */
	fc_CloneRowTriggersToPartition(fc_rel, fc_attachrel);

	/*
	 * 克隆外键约束。被调用者负责设置阶段 3 的约束验证。
	 */
	fc_CloneForeignKeyConstraints(fc_wqueue, fc_rel, fc_attachrel);

	/*
	 * 从分区边界规范生成分区约束。
	 * 如果父表本身是一个分区，确保包含其约束。
	 */
	fc_partBoundConstraint = get_qual_from_partbound(fc_rel, fc_cmd->bound);
	fc_partConstraint = list_concat(fc_partBoundConstraint,
								 RelationGetPartitionQual(fc_rel));

	/* 如果没有约束需要验证，则跳过验证。 */
	if (fc_partConstraint)
	{
		/*
		 * 通过类似检查约束的常量简化运行分区资格。
		 * 不过，我们跳过 canonicalize_qual，因为分区资格应已经是规范形式。
		 */
		fc_partConstraint =
			(List *) eval_const_expressions(NULL,
											(Node *) fc_partConstraint);

		/* XXX 这看起来确实有问题 */
		fc_partConstraint = list_make1(make_ands_explicit(fc_partConstraint));

		/*
		 * 调整生成的约束以匹配该分区的属性编号。
		 */
		fc_partConstraint = map_partition_varattnos(fc_partConstraint, 1, fc_attachrel,
												 fc_rel);

		/* 验证分区约束与正在附加的表之间的一致性。 */
		fc_QueuePartitionConstraintValidation(fc_wqueue, fc_attachrel, fc_partConstraint,
										   false);
	}

	/*
	 * 如果我们正在附加一个不同于默认分区的分区，并且默认分区存在，
	 * 则该分区的分区约束会改变，因此也要向工作队列添加一个条目以验证它。
	 * （当附加的分区是默认分区时，我们必须不这样做；我们已经在上面做过了！）
	 */
	if (OidIsValid(fc_defaultPartOid))
	{
		Relation	fc_defaultrel;
		List	   *fc_defPartConstraint;

		Assert(!fc_cmd->bound->is_default);

		/* 我们已经持有默认分区的锁 */
		fc_defaultrel = table_open(fc_defaultPartOid, NoLock);
		fc_defPartConstraint =
			get_proposed_default_constraint(fc_partBoundConstraint);

		/*
		 * 将约束表达式中的变量从 rel 的 attnos 映射到 defaultrel 的。
		 */
		fc_defPartConstraint =
			map_partition_varattnos(fc_defPartConstraint,
									1, fc_defaultrel, fc_rel);
		fc_QueuePartitionConstraintValidation(fc_wqueue, fc_defaultrel,
										   fc_defPartConstraint, true);

		/* 在提交之前保持我们的锁。 */
		table_close(fc_defaultrel, NoLock);
	}

	ObjectAddressSet(fc_address, RelationRelationId, RelationGetRelid(fc_attachrel));

	/*
	 * 如果我们刚附加的分区本身是分区的，必须使所有下级分区的 relcache 无效，
	 * 以确保重建它们的 rd_partcheck 表达式树；在该函数开始时已经锁定的分区。
	 */
	if (fc_attachrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
	{
		ListCell   *fc_l;

		foreach(fc_l, fc_attachrel_children)
		{
			CacheInvalidateRelcacheByRelid(lfirst_oid(fc_l));
		}
	}

	/* 在提交之前保持我们的锁 */
	table_close(fc_attachrel, NoLock);

	return fc_address;
}

/*
 * AttachPartitionEnsureIndexes
 *		ATExecAttachPartition 的子例程，用于创建/匹配索引
 *
 * 在 ALTER TABLE / ATTACH PARTITION 过程中强制分区表的索引规则：
 * 每个分区必须附加到分区表上的每个索引。
 */
static void fc_AttachPartitionEnsureIndexes(Relation fc_rel, Relation fc_attachrel)
{
	List	   *fc_idxes;
	List	   *fc_attachRelIdxs;
	Relation   *fc_attachrelIdxRels;
	IndexInfo **fc_attachInfos;
	int			fc_i;
	ListCell   *fc_cell;
	MemoryContext fc_cxt;
	MemoryContext fc_oldcxt;

	fc_cxt = AllocSetContextCreate(CurrentMemoryContext,
								"AttachPartitionEnsureIndexes",
								ALLOCSET_DEFAULT_SIZES);
	fc_oldcxt = MemoryContextSwitchTo(fc_cxt);

	fc_idxes = RelationGetIndexList(fc_rel);
	fc_attachRelIdxs = RelationGetIndexList(fc_attachrel);
	fc_attachrelIdxRels = palloc(sizeof(Relation) * list_length(fc_attachRelIdxs));
	fc_attachInfos = palloc(sizeof(IndexInfo *) * list_length(fc_attachRelIdxs));

	/* 构建所有现有索引及其 IndexInfos 的数组 */
	fc_i = 0;
	foreach(fc_cell, fc_attachRelIdxs)
	{
		Oid			fc_cldIdxId = lfirst_oid(fc_cell);

		fc_attachrelIdxRels[fc_i] = index_open(fc_cldIdxId, AccessShareLock);
		fc_attachInfos[fc_i] = BuildIndexInfo(fc_attachrelIdxRels[fc_i]);
		fc_i++;
	}

	/*
	 * 如果我们正在附加一个外部表，如果任何索引是约束索引，则必须失败；
	 * 否则，这里没有什么可做的。在开始工作之前执行此操作，以避免在遇到唯一索引之前
	 * 构建一些非唯一索引而浪费精力。
	 */
	if (fc_attachrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
	{
		foreach(fc_cell, fc_idxes)
		{
			Oid			fc_idx = lfirst_oid(fc_cell);
			Relation	fc_idxRel = index_open(fc_idx, AccessShareLock);

			if (fc_idxRel->rd_index->indisunique ||
				fc_idxRel->rd_index->indisprimary)
				ereport(ERROR,
						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
						 errmsg("cannot attach foreign table \"%s\" as partition of partitioned table \"%s\"",
								RelationGetRelationName(fc_attachrel),
								RelationGetRelationName(fc_rel)),
						 errdetail("Partitioned table \"%s\" contains unique indexes.",
								   RelationGetRelationName(fc_rel))));
			index_close(fc_idxRel, AccessShareLock);
		}

		goto out;
	}

	/*
	 * 对于分区表上的每个索引，在待分区中找到一个匹配的索引；
	 * 如果没有找到，则创建一个。
	 */
	foreach(fc_cell, fc_idxes)
	{
		Oid			fc_idx = lfirst_oid(fc_cell);
		Relation	fc_idxRel = index_open(fc_idx, AccessShareLock);
		IndexInfo  *fc_info;
		AttrMap    *fc_attmap;
		bool		fc_found = false;
		Oid			fc_constraintOid;

		/*
		 * 忽略分区表中除分区索引以外的索引。
		 */
		if (fc_idxRel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
		{
			index_close(fc_idxRel, AccessShareLock);
			continue;
		}

		/* 构造一个 indexinfo 以与现有索引进行比较 */
		fc_info = BuildIndexInfo(fc_idxRel);
		fc_attmap = build_attrmap_by_name(RelationGetDescr(fc_attachrel),
									   RelationGetDescr(fc_rel));
		fc_constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(fc_rel), fc_idx);

		/*
		 * 扫描待分区中的现有索引列表，并标记第一个匹配的、有效的、未附加的索引（如果有的话），
		 * 作为父索引的分区。如果找到一个，我们就完成了。
		 */
		for (fc_i = 0; fc_i < list_length(fc_attachRelIdxs); fc_i++)
		{
			Oid			fc_cldIdxId = RelationGetRelid(fc_attachrelIdxRels[fc_i]);
			Oid			fc_cldConstrOid = InvalidOid;

			/* 这个索引有父级吗？如果有，不能使用它 */
			if (fc_attachrelIdxRels[fc_i]->rd_rel->relispartition)
				continue;

			/* 如果这个索引无效，不能使用它 */
			if (!fc_attachrelIdxRels[fc_i]->rd_index->indisvalid)
				continue;

			if (CompareIndexInfo(fc_attachInfos[fc_i], fc_info,
								 fc_attachrelIdxRels[fc_i]->rd_indcollation,
								 fc_idxRel->rd_indcollation,
								 fc_attachrelIdxRels[fc_i]->rd_opfamily,
								 fc_idxRel->rd_opfamily,
								 fc_attmap))
			{
				/*
				 * 如果由于约束在父级中创建了这个索引，
				 * 则子级也需要有一个约束，因此寻找一个。
				 * 如果没有这样的约束，这个索引就没用，所以继续寻找。
				 */
				if (OidIsValid(fc_constraintOid))
				{
					fc_cldConstrOid =
						get_relation_idx_constraint_oid(RelationGetRelid(fc_attachrel),
														fc_cldIdxId);
					/* 没有运气 */
					if (!OidIsValid(fc_cldConstrOid))
						continue;
				}

				/* 一箭双雕。 */
				IndexSetParentIndex(fc_attachrelIdxRels[fc_i], fc_idx);
				if (OidIsValid(fc_constraintOid))
					ConstraintSetParentConstraint(fc_cldConstrOid, fc_constraintOid,
												  RelationGetRelid(fc_attachrel));
				fc_found = true;

				CommandCounterIncrement();
				break;
			}
		}

		/*
		 * 如果在待分区中没有找到合适的索引，现在就创建一个。
		 */
		if (!fc_found)
		{
			IndexStmt  *fc_stmt;
			Oid			fc_constraintOid;

			fc_stmt = generateClonedIndexStmt(NULL,
										   fc_idxRel, fc_attmap,
										   &fc_constraintOid);
			DefineIndex(RelationGetRelid(fc_attachrel), fc_stmt, InvalidOid,
						RelationGetRelid(fc_idxRel),
						fc_constraintOid,
						true, false, false, false, false);
		}

		index_close(fc_idxRel, AccessShareLock);
	}

out:
	/* 清理。 */
	for (fc_i = 0; fc_i < list_length(fc_attachRelIdxs); fc_i++)
		index_close(fc_attachrelIdxRels[fc_i], AccessShareLock);
	MemoryContextSwitchTo(fc_oldcxt);
	MemoryContextDelete(fc_cxt);
}

/*
 * CloneRowTriggersToPartition
 *		ATExecAttachPartition/DefineRelation 的子例程，用于在分区上创建行触发器
 */
static void fc_CloneRowTriggersToPartition(Relation fc_parent, Relation fc_partition)
{
	Relation	fc_pg_trigger;
	ScanKeyData fc_key;
	SysScanDesc fc_scan;
	HeapTuple	fc_tuple;
	MemoryContext fc_perTupCxt;

	ScanKeyInit(&fc_key, Anum_pg_trigger_tgrelid, BTEqualStrategyNumber,
				F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(fc_parent)));
	fc_pg_trigger = table_open(TriggerRelationId, RowExclusiveLock);
	fc_scan = systable_beginscan(fc_pg_trigger, TriggerRelidNameIndexId,
							  true, NULL, 1, &fc_key);

	fc_perTupCxt = AllocSetContextCreate(CurrentMemoryContext,
									  "clone trig", ALLOCSET_SMALL_SIZES);

	while (HeapTupleIsValid(fc_tuple = systable_getnext(fc_scan)))
	{
		Form_pg_trigger fc_trigForm = (Form_pg_trigger) GETSTRUCT(fc_tuple);
		CreateTrigStmt *fc_trigStmt;
		Node	   *fc_qual = NULL;
		Datum		fc_value;
		bool		fc_isnull;
		List	   *fc_cols = NIL;
		List	   *fc_trigargs = NIL;
		MemoryContext fc_oldcxt;

		/*
		 * 忽略语句级触发器；这些不会被克隆。
		 */
		if (!TRIGGER_FOR_ROW(fc_trigForm->tgtype))
			continue;

		/*
		 * 不要克隆内部触发器，因为约束克隆代码会克隆它们。
		 */
		if (fc_trigForm->tgisinternal)
			continue;

		/*
		 * 如果发现意外的触发器类型，请投诉。
		 */
		if (!TRIGGER_FOR_BEFORE(fc_trigForm->tgtype) &&
			!TRIGGER_FOR_AFTER(fc_trigForm->tgtype))
			elog(ERROR, "unexpected trigger \"%s\" found",
				 NameStr(fc_trigForm->tgname));

		/* 对于 CREATE TRIGGER 使用短暂的上下文 */
		fc_oldcxt = MemoryContextSwitchTo(fc_perTupCxt);

		/*
		 * 如果存在WHEN子句，生成一个适合于分区的'烹饪'版本。
		 */
		fc_value = heap_getattr(fc_tuple, Anum_pg_trigger_tgqual,
							 RelationGetDescr(fc_pg_trigger), &fc_isnull);
		if (!fc_isnull)
		{
			fc_qual = stringToNode(TextDatumGetCString(fc_value));
			fc_qual = (Node *) map_partition_varattnos((List *) fc_qual, PRS2_OLD_VARNO,
													fc_partition, fc_parent);
			fc_qual = (Node *) map_partition_varattnos((List *) fc_qual, PRS2_NEW_VARNO,
													fc_partition, fc_parent);
		}

		/*
		 * 如果存在列列表，将其转换为列名列表。
		 * 注意我们不需要以任何方式映射此列表...
		 */
		if (fc_trigForm->tgattr.dim1 > 0)
		{
			int			fc_i;

			for (fc_i = 0; fc_i < fc_trigForm->tgattr.dim1; fc_i++)
			{
				Form_pg_attribute fc_col;

				fc_col = TupleDescAttr(fc_parent->rd_att,
									fc_trigForm->tgattr.values[fc_i] - 1);
				fc_cols = lappend(fc_cols,
							   makeString(pstrdup(NameStr(fc_col->attname))));
			}
		}

		/* 重新构建触发器参数列表。 */
		if (fc_trigForm->tgnargs > 0)
		{
			char	   *fc_p;

			fc_value = heap_getattr(fc_tuple, Anum_pg_trigger_tgargs,
								 RelationGetDescr(fc_pg_trigger), &fc_isnull);
			if (fc_isnull)
				elog(ERROR, "tgargs is null for trigger \"%s\" in partition \"%s\"",
					 NameStr(fc_trigForm->tgname), RelationGetRelationName(fc_partition));

			fc_p = (char *) VARDATA_ANY(DatumGetByteaPP(fc_value));

			for (int fc_i = 0; fc_i < fc_trigForm->tgnargs; fc_i++)
			{
				fc_trigargs = lappend(fc_trigargs, makeString(pstrdup(fc_p)));
				fc_p += strlen(fc_p) + 1;
			}
		}

		fc_trigStmt = makeNode(CreateTrigStmt);
		fc_trigStmt->replace = false;
		fc_trigStmt->isconstraint = OidIsValid(fc_trigForm->tgconstraint);
		fc_trigStmt->trigname = NameStr(fc_trigForm->tgname);
		fc_trigStmt->relation = NULL;
		fc_trigStmt->funcname = NULL;	/* 单独传递 */
		fc_trigStmt->args = fc_trigargs;
		fc_trigStmt->row = true;
		fc_trigStmt->timing = fc_trigForm->tgtype & TRIGGER_TYPE_TIMING_MASK;
		fc_trigStmt->events = fc_trigForm->tgtype & TRIGGER_TYPE_EVENT_MASK;
		fc_trigStmt->columns = fc_cols;
		fc_trigStmt->whenClause = NULL;	/* 单独传递 */
		fc_trigStmt->transitionRels = NIL; /* 目前不支持 */
		fc_trigStmt->deferrable = fc_trigForm->tgdeferrable;
		fc_trigStmt->initdeferred = fc_trigForm->tginitdeferred;
		fc_trigStmt->constrrel = NULL; /* 单独传递 */

		CreateTriggerFiringOn(fc_trigStmt, NULL, RelationGetRelid(fc_partition),
							  fc_trigForm->tgconstrrelid, InvalidOid, InvalidOid,
							  fc_trigForm->tgfoid, fc_trigForm->oid, fc_qual,
							  false, true, fc_trigForm->tgenabled);

		MemoryContextSwitchTo(fc_oldcxt);
		MemoryContextReset(fc_perTupCxt);
	}

	MemoryContextDelete(fc_perTupCxt);

	systable_endscan(fc_scan);
	table_close(fc_pg_trigger, RowExclusiveLock);
}

/*
 * ALTER TABLE DETACH PARTITION
 *
 * 返回不再是rel的分区的关系地址。
 *
 * 如果请求并发模式，我们在两个事务中运行。一个副作用是该命令无法在多部分ALTER TABLE中运行。
 * 目前，这是由语法强制执行的。
 *
 * 并发的策略是首先修改分区的pg_inherit目录行，使其对所有人可见，该分区已被分离，锁定该分区以防写入，并提交事务；从那时起，任何请求该分区描述符的人都必须忽略这样的分区。在第二个事务中，等待所有可能看到分区作为附加的事务完成，然后移除其余的分区元数据（pg_inherits和pg_class.relpartbounds）。
 */
static ObjectAddress fc_ATExecDetachPartition(List **fc_wqueue, AlteredTableInfo *fc_tab, Relation fc_rel,
					  RangeVar *fc_name, bool fc_concurrent)
{
	Relation	fc_partRel;
	ObjectAddress fc_address;
	Oid			fc_defaultPartOid;

	/*
	 * 我们必须锁定默认分区，因为分离此分区将改变其分区约束。
	 */
	fc_defaultPartOid =
		get_default_oid_from_partdesc(RelationGetPartitionDesc(fc_rel, true));
	if (OidIsValid(fc_defaultPartOid))
	{
		/*
		 * 当存在默认分区时，不支持并发分离。主要问题是默认分区约束会改变。而且还有一个定义性的问题：要分离的分区中的插入元组该怎么办？将它们放在要分离的分区上是不对的，因为它们在分离完成后会变得“丢失”，但我们也不能将它们放在默认分区上，直到我们更改其分区约束。
		 *
		 * 我认为如果在提交第一个事务之前改变约束，我们可以解决这个问题。但是，锁必须保持为AEL，这将导致并发查询规划被阻塞，因此以这种方式改变锁将更糟。
		 */
		if (fc_concurrent)
			ereport(ERROR,
					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
					 errmsg("cannot detach partitions concurrently when a default partition exists")));
		LockRelationOid(fc_defaultPartOid, AccessExclusiveLock);
	}

	/*
	 * 在并发模式中，分区在第一个事务中以share-update-exclusive锁定。
	 * 这允许并发事务对分区进行DML操作。
	 */
	fc_partRel = table_openrv(fc_name, fc_concurrent ? ShareUpdateExclusiveLock :
						   AccessExclusiveLock);

	/*
	 * 检查继承条件，或者删除pg_inherits行（在非并发模式下），或者仅设置inhdetachpending标志。
	 */
	if (!fc_concurrent)
		fc_RemoveInheritance(fc_partRel, fc_rel, false);
	else
		fc_MarkInheritDetached(fc_partRel, fc_rel);

	/*
	 * 确保在此分离后外键仍然有效。这保持对引用表的锁定，这会防止并发事务添加我们看不到的行。为了在并发模式下实现这一点，重要的是分区在第一个事务提交后立即对RI查询显示为不再附加。
	 */
	fc_ATDetachCheckNoForeignKeyRefs(fc_partRel);

	/*
	 * 并发模式需要更加努力；首先，我们向
	 * 匹配分区约束的分区添加一个新约束。然后我们关闭现有的
	 * 事务，在一个新的事务中等待所有进程追赶
	 * 我们到目前为止所做的目录更新；到那时我们可以
	 * 完成操作。
	 */
	if (fc_concurrent)
	{
		Oid			fc_partrelid,
					fc_parentrelid;
		LOCKTAG		fc_tag;
		char	   *fc_parentrelname;
		char	   *fc_partrelname;

		/*
		 * 向正在分离的分区添加一个新约束，该约束
		 * 取代分区约束（除非已经存在一个）。
		 */
		fc_DetachAddConstraintIfNeeded(fc_wqueue, fc_partRel);

		/*
		 * 我们快完成了；唯一剩下的痕迹是
		 * pg_inherits 元组和分区的 relpartbounds。在我们可以
		 * 删除这些之前，我们需要等待所有知道这是
		 * 分区的事务完成。
		 */

		/*
		 * 记住关系 OID，以便稍后重新获取；以及关系名称
		 * 用于错误消息，如果在此期间有东西被删除。
		 */
		fc_partrelid = RelationGetRelid(fc_partRel);
		fc_parentrelid = RelationGetRelid(fc_rel);
		fc_parentrelname = MemoryContextStrdup(PortalContext,
											RelationGetRelationName(fc_rel));
		fc_partrelname = MemoryContextStrdup(PortalContext,
										  RelationGetRelationName(fc_partRel));

		/* 使父项的 relcache 条目失效 -- 必须在关闭之前 */
		CacheInvalidateRelcache(fc_rel);

		table_close(fc_partRel, NoLock);
		table_close(fc_rel, NoLock);
		fc_tab->rel = NULL;

		/* 使更新的目录条目可见 */
		PopActiveSnapshot();
		CommitTransactionCommand();

		StartTransactionCommand();

		/*
		 * 现在等待。这确保了所有计划的查询
		 * 包括该分区在我们移除其他
		 * 目录条目之前完成。我们无须，也不希望获得这个
		 * 锁定，因为这会阻塞后续查询。
		 *
		 * 我们不需担心等待分区本身的锁定，
		 * 因为我们将在下面获得 AccessExclusiveLock。
		 */
		SET_LOCKTAG_RELATION(fc_tag, MyDatabaseId, fc_parentrelid);
		WaitForLockersMultiple(list_make1(&fc_tag), AccessExclusiveLock, false);

		/*
		 * 现在再次获取两个关系的锁。注意，它们可能在
		 * 此期间已被移除，因此需要小心。
		 */
		fc_rel = try_relation_open(fc_parentrelid, ShareUpdateExclusiveLock);
		fc_partRel = try_relation_open(fc_partrelid, AccessExclusiveLock);

		/* 如果关系不在，则发生了错误；退出 */
		if (fc_rel == NULL)
		{
			if (fc_partRel != NULL)	/* 不应该发生 */
				elog(WARNING, "dangling partition \"%s\" remains, can't fix",
					 fc_partrelname);
			ereport(ERROR,
					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
					 errmsg("partitioned table \"%s\" was removed concurrently",
							fc_parentrelname)));
		}
		if (fc_partRel == NULL)
			ereport(ERROR,
					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
					 errmsg("partition \"%s\" was removed concurrently", fc_partrelname)));

		fc_tab->rel = fc_rel;
	}

	/* 执行分离的最后部分 */
	fc_DetachPartitionFinalize(fc_rel, fc_partRel, fc_concurrent, fc_defaultPartOid);

	ObjectAddressSet(fc_address, RelationRelationId, RelationGetRelid(fc_partRel));

	/* 在提交之前保持我们的锁 */
	table_close(fc_partRel, NoLock);

	return fc_address;
}

/*
 * ALTER TABLE .. DETACH 的第二部分。
 *
 * 这是分开的，以便在并发算法的第二个
 * 事务失败（崩溃或中止）时可以独立运行。
 */
static void fc_DetachPartitionFinalize(Relation fc_rel, Relation fc_partRel, bool fc_concurrent,
						Oid fc_defaultPartOid)
{
	Relation	fc_classRel;
	List	   *fc_fks;
	ListCell   *fc_cell;
	List	   *fc_indexes;
	Datum		fc_new_val[Natts_pg_class];
	bool		fc_new_null[Natts_pg_class],
				fc_new_repl[Natts_pg_class];
	HeapTuple	fc_tuple,
				fc_newtuple;
	Relation	fc_trigrel = NULL;
	List	   *fc_fkoids = NIL;

	if (fc_concurrent)
	{
		/*
		 * 我们现在可以删除 pg_inherits 行。（在非并发情况下，
		 * 这已经完成）。
		 */
		fc_RemoveInheritance(fc_partRel, fc_rel, true);
	}

	/* 删除在创建/附加时克隆的任何触发器。 */
	fc_DropClonedTriggersFromPartition(RelationGetRelid(fc_partRel));

	/*
	 * 分离任何继承的外键。这包括创建
	 * 额外的操作触发器。
	 */
	fc_fks = copyObject(RelationGetFKeyList(fc_partRel));
	if (fc_fks != NIL)
		fc_trigrel = table_open(TriggerRelationId, RowExclusiveLock);

	/*
	 * 被分离的分区可能有一个引用分区表的外键。
	 * 当发生这种情况时，分区有多个 pg_constraint 行：
	 * 一个指向分区表本身，而其他指向它的每个分区。
	 * 这里只考虑最上面的一个；子约束必须
	 * 保持不变，因为从概念上讲，那些并不是来自我们的父
	 * 分区表，而是来自这个分区本身。
	 *
	 * 我们通过在 FK 数组的第一次扫描中收集所有约束 OID
	 * 来实现这一点，并在下面的循环中跳过那些
	 * 父级在这里列出的约束。
	 */
	foreach(fc_cell, fc_fks)
	{
		ForeignKeyCacheInfo *fc_fk = (ForeignKeyCacheInfo *) lfirst(fc_cell);

		fc_fkoids = lappend_oid(fc_fkoids, fc_fk->conoid);
	}

	foreach(fc_cell, fc_fks)
	{
		ForeignKeyCacheInfo *fc_fk = lfirst(fc_cell);
		HeapTuple	fc_contup;
		Form_pg_constraint fc_conform;
		Oid			fc_insertTriggerOid,
					fc_updateTriggerOid;

		fc_contup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fc_fk->conoid));
		if (!HeapTupleIsValid(fc_contup))
			elog(ERROR, "cache lookup failed for constraint %u", fc_fk->conoid);
		fc_conform = (Form_pg_constraint) GETSTRUCT(fc_contup);

		/*
		 * 只考虑继承的外键，并且只有在它们的父级不在列表中时。
		 */
		if (fc_conform->contype != CONSTRAINT_FOREIGN ||
			!OidIsValid(fc_conform->conparentid) ||
			list_member_oid(fc_fkoids, fc_conform->conparentid))
		{
			ReleaseSysCache(fc_contup);
			continue;
		}

		/*
		 * 此表上的约束必须标记为不再是父级约束的子约束，检查触发器也要如此。
		 */
		ConstraintSetParentConstraint(fc_fk->conoid, InvalidOid, InvalidOid);

		/*
		 * 此外，查找与被分离的约束对应的分区的“检查”触发器，并将它们从父触发器中分离。
		 */
		fc_GetForeignKeyCheckTriggers(fc_trigrel,
								   fc_fk->conoid, fc_fk->confrelid, fc_fk->conrelid,
								   &fc_insertTriggerOid, &fc_updateTriggerOid);
		Assert(OidIsValid(fc_insertTriggerOid));
		TriggerSetParentTrigger(fc_trigrel, fc_insertTriggerOid, InvalidOid,
								RelationGetRelid(fc_partRel));
		Assert(OidIsValid(fc_updateTriggerOid));
		TriggerSetParentTrigger(fc_trigrel, fc_updateTriggerOid, InvalidOid,
								RelationGetRelid(fc_partRel));

		/*
		 * 最后，在被引用的表上创建操作触发器，使用 addFkRecurseReferenced，这需要一些复杂的设置（所以放在一个单独的代码块中）。如果该表是分区的，那么该函数将递归创建每个分区的 pg_constraint 行和操作触发器。
		 *
		 * 注意，这里不需要执行 addFkConstraint()，因为 pg_constraint 行已经存在。
		 */
		{
			Constraint *fc_fkconstraint;
			int			fc_numfks;
			AttrNumber	fc_conkey[INDEX_MAX_KEYS];
			AttrNumber	fc_confkey[INDEX_MAX_KEYS];
			Oid			fc_conpfeqop[INDEX_MAX_KEYS];
			Oid			fc_conppeqop[INDEX_MAX_KEYS];
			Oid			fc_conffeqop[INDEX_MAX_KEYS];
			int			fc_numfkdelsetcols;
			AttrNumber	fc_confdelsetcols[INDEX_MAX_KEYS];
			Relation	fc_refdRel;

			DeconstructFkConstraintRow(fc_contup,
									   &fc_numfks,
									   fc_conkey,
									   fc_confkey,
									   fc_conpfeqop,
									   fc_conppeqop,
									   fc_conffeqop,
									   &fc_numfkdelsetcols,
									   fc_confdelsetcols);

			/* 创建一个我们将始终使用的合成节点 */
			fc_fkconstraint = makeNode(Constraint);
			fc_fkconstraint->contype = CONSTRAINT_FOREIGN;
			fc_fkconstraint->conname = pstrdup(NameStr(fc_conform->conname));
			fc_fkconstraint->deferrable = fc_conform->condeferrable;
			fc_fkconstraint->initdeferred = fc_conform->condeferred;
			fc_fkconstraint->skip_validation = true;
			fc_fkconstraint->initially_valid = true;
			/* 此处省略了一些无关字段 */
			fc_fkconstraint->pktable = NULL;
			fc_fkconstraint->fk_attrs = NIL;
			fc_fkconstraint->pk_attrs = NIL;
			fc_fkconstraint->fk_matchtype = fc_conform->confmatchtype;
			fc_fkconstraint->fk_upd_action = fc_conform->confupdtype;
			fc_fkconstraint->fk_del_action = fc_conform->confdeltype;
			fc_fkconstraint->fk_del_set_cols = NIL;
			fc_fkconstraint->old_conpfeqop = NIL;
			fc_fkconstraint->old_pktable_oid = InvalidOid;
			fc_fkconstraint->location = -1;

			/* 设置列名，用于生成约束名称 */
			for (int fc_i = 0; fc_i < fc_numfks; fc_i++)
			{
				Form_pg_attribute fc_att;

				fc_att = TupleDescAttr(RelationGetDescr(fc_partRel),
									fc_conkey[fc_i] - 1);

				fc_fkconstraint->fk_attrs = lappend(fc_fkconstraint->fk_attrs,
												 makeString(NameStr(fc_att->attname)));
			}

			fc_refdRel = table_open(fc_fk->confrelid, ShareRowExclusiveLock);

			fc_addFkRecurseReferenced(fc_fkconstraint, fc_partRel,
								   fc_refdRel,
								   fc_conform->conindid,
								   fc_fk->conoid,
								   fc_numfks,
								   fc_confkey,
								   fc_conkey,
								   fc_conpfeqop,
								   fc_conppeqop,
								   fc_conffeqop,
								   fc_numfkdelsetcols,
								   fc_confdelsetcols,
								   true,
								   InvalidOid, InvalidOid);
			table_close(fc_refdRel, NoLock);	/* 锁定直到事务结束 */
		}

		ReleaseSysCache(fc_contup);
	}
	list_free_deep(fc_fks);
	if (fc_trigrel)
		table_close(fc_trigrel, RowExclusiveLock);

	/*
	 * 任何位于更大约束的引用侧的子约束都必须被移除。该分区不再是约束的关键空间的一部分。
	 */
	foreach(fc_cell, fc_GetParentedForeignKeyRefs(fc_partRel))
	{
		Oid			fc_constrOid = lfirst_oid(fc_cell);
		ObjectAddress fc_constraint;

		ConstraintSetParentConstraint(fc_constrOid, InvalidOid, InvalidOid);
		deleteDependencyRecordsForClass(ConstraintRelationId,
										fc_constrOid,
										ConstraintRelationId,
										DEPENDENCY_INTERNAL);
		CommandCounterIncrement();

		ObjectAddressSet(fc_constraint, ConstraintRelationId, fc_constrOid);
		performDeletion(&fc_constraint, DROP_RESTRICT, 0);
	}

	/* 现在我们可以分离索引 */
	fc_indexes = RelationGetIndexList(fc_partRel);
	foreach(fc_cell, fc_indexes)
	{
		Oid			fc_idxid = lfirst_oid(fc_cell);
		Oid			fc_parentidx;
		Relation	fc_idx;
		Oid			fc_constrOid;
		Oid			fc_parentConstrOid;

		if (!has_superclass(fc_idxid))
			continue;

		fc_parentidx = get_partition_parent(fc_idxid, false);
		Assert((IndexGetRelation(fc_parentidx, false) == RelationGetRelid(fc_rel)));

		fc_idx = index_open(fc_idxid, AccessExclusiveLock);
		IndexSetParentIndex(fc_idx, InvalidOid);

		/*
		 * 如果与索引相关联的约束，则也要分离它。小心：一个分区中的约束索引可能是非约束索引的子索引，因此要验证父索引确实具有约束。
		 */
		fc_constrOid = get_relation_idx_constraint_oid(RelationGetRelid(fc_partRel),
													fc_idxid);
		fc_parentConstrOid = get_relation_idx_constraint_oid(RelationGetRelid(fc_rel),
														  fc_parentidx);
		if (OidIsValid(fc_parentConstrOid) && OidIsValid(fc_constrOid))
			ConstraintSetParentConstraint(fc_constrOid, InvalidOid, InvalidOid);

		index_close(fc_idx, NoLock);
	}

	/* 更新 pg_class 元组 */
	fc_classRel = table_open(RelationRelationId, RowExclusiveLock);
	fc_tuple = SearchSysCacheCopy1(RELOID,
								ObjectIdGetDatum(RelationGetRelid(fc_partRel)));
	if (!HeapTupleIsValid(fc_tuple))
		elog(ERROR, "cache lookup failed for relation %u",
			 RelationGetRelid(fc_partRel));
	Assert(((Form_pg_class) GETSTRUCT(fc_tuple))->relispartition);

	/* 清除 relpartbound 并重置 relispartition */
	memset(fc_new_val, 0, sizeof(fc_new_val));
	memset(fc_new_null, false, sizeof(fc_new_null));
	memset(fc_new_repl, false, sizeof(fc_new_repl));
	fc_new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0;
	fc_new_null[Anum_pg_class_relpartbound - 1] = true;
	fc_new_repl[Anum_pg_class_relpartbound - 1] = true;
	fc_newtuple = heap_modify_tuple(fc_tuple, RelationGetDescr(fc_classRel),
								 fc_new_val, fc_new_null, fc_new_repl);

	((Form_pg_class) GETSTRUCT(fc_newtuple))->relispartition = false;
	CatalogTupleUpdate(fc_classRel, &fc_newtuple->t_self, fc_newtuple);
	heap_freetuple(fc_newtuple);
	table_close(fc_classRel, RowExclusiveLock);

	if (OidIsValid(fc_defaultPartOid))
	{
		/*
		 * 如果正在分离的关系是默认分区本身，则将其从父级的 pg_partitioned_table 条目中移除。
		 *
		 * 如果不是，我们必须使默认分区的 relcache 条目失效，如 StorePartitionBound 所示：其分区约束依赖于每个其他分区的分区约束。
		 */
		if (RelationGetRelid(fc_partRel) == fc_defaultPartOid)
			update_default_partition_oid(RelationGetRelid(fc_rel), InvalidOid);
		else
			CacheInvalidateRelcacheByRelid(fc_defaultPartOid);
	}

	/*
	 * 使父级的 relcache 无效，以便该分区不再包含在其分区描述符中。
	 */
	CacheInvalidateRelcache(fc_rel);

	/*
	 * 如果我们刚刚分离的分区本身是分区的，还必须使所有后代分区的 relcache 无效，以确保它们的 rd_partcheck 表达式树被重建；在此之前必须锁定分区，使用与调用者锁定 partRel 时相同的锁定模式。
	 */
	if (fc_partRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
	{
		List	   *fc_children;

		fc_children = find_all_inheritors(RelationGetRelid(fc_partRel),
									   AccessExclusiveLock, NULL);
		foreach(fc_cell, fc_children)
		{
			CacheInvalidateRelcacheByRelid(lfirst_oid(fc_cell));
		}
	}
}

/*
 * ALTER TABLE ... DETACH PARTITION ... FINALIZE
 *
 * 用于当 DETACH PARTITION 命令之前未运行完成时；这将完成分离过程。
 */
static ObjectAddress fc_ATExecDetachPartitionFinalize(Relation fc_rel, RangeVar *fc_name)
{
	Relation	fc_partRel;
	ObjectAddress fc_address;
	Snapshot	fc_snap = GetActiveSnapshot();

	fc_partRel = table_openrv(fc_name, AccessExclusiveLock);

	/*
	 * 等待直到现有快照消失。如果 DETACH PARTITION CONCURRENTLY 的第二个事务被取消，这一点非常重要：
	 * 用户可能会立即跑 DETACH FINALIZE，而实际上并没有等待现有事务。我们必须在所有这些查询完成之前不能完成 detach 动作
	 * （否则我们会向他们展示一个不一致的目录视图）。
	 */
	WaitForOlderSnapshots(fc_snap->xmin, false);

	fc_DetachPartitionFinalize(fc_rel, fc_partRel, true, InvalidOid);

	ObjectAddressSet(fc_address, RelationRelationId, RelationGetRelid(fc_partRel));

	table_close(fc_partRel, NoLock);

	return fc_address;
}

/*
 * DetachAddConstraintIfNeeded
 *		ATExecDetachPartition 的子例程。创建一个替代分区约束的约束，但如果已经存在一个暗示所需约束的约束，则避免创建重复。
 */
static void fc_DetachAddConstraintIfNeeded(List **fc_wqueue, Relation fc_partRel)
{
	List	   *fc_constraintExpr;

	fc_constraintExpr = RelationGetPartitionQual(fc_partRel);
	fc_constraintExpr = (List *) eval_const_expressions(NULL, (Node *) fc_constraintExpr);

	/*
	 * 如果现有约束暗示所需约束，则避免添加新约束。
	 */
	if (!PartConstraintImpliedByRelConstraint(fc_partRel, fc_constraintExpr))
	{
		AlteredTableInfo *fc_tab;
		Constraint *fc_n;

		fc_tab = fc_ATGetQueueEntry(fc_wqueue, fc_partRel);

		/* 在分区上添加约束，相当于分区约束 */
		fc_n = makeNode(Constraint);
		fc_n->contype = CONSTR_CHECK;
		fc_n->conname = NULL;
		fc_n->location = -1;
		fc_n->is_no_inherit = false;
		fc_n->raw_expr = NULL;
		fc_n->cooked_expr = nodeToString(make_ands_explicit(fc_constraintExpr));
		fc_n->initially_valid = true;
		fc_n->skip_validation = true;
		/* 这是重新添加，因为它名义上已经存在 */
		fc_ATAddCheckConstraint(fc_wqueue, fc_tab, fc_partRel, fc_n,
							 true, false, true, ShareUpdateExclusiveLock);
	}
}

/*
 * DropClonedTriggersFromPartition
 *		ATExecDetachPartition 的子例程，用于移除在分区创建或附加时克隆到分区的任何触发器。
 *		这撤销了 CloneRowTriggersToPartition 的操作。
 */
static void fc_DropClonedTriggersFromPartition(Oid fc_partitionId)
{
	ScanKeyData fc_skey;
	SysScanDesc fc_scan;
	HeapTuple	fc_trigtup;
	Relation	fc_tgrel;
	ObjectAddresses *fc_objects;

	fc_objects = new_object_addresses();

	/*
	 * 扫描 pg_trigger 以搜索此关系上所有的触发器。
	 */
	ScanKeyInit(&fc_skey, Anum_pg_trigger_tgrelid, BTEqualStrategyNumber,
				F_OIDEQ, ObjectIdGetDatum(fc_partitionId));
	fc_tgrel = table_open(TriggerRelationId, RowExclusiveLock);
	fc_scan = systable_beginscan(fc_tgrel, TriggerRelidNameIndexId,
							  true, NULL, 1, &fc_skey);
	while (HeapTupleIsValid(fc_trigtup = systable_getnext(fc_scan)))
	{
		Form_pg_trigger fc_pg_trigger = (Form_pg_trigger) GETSTRUCT(fc_trigtup);
		ObjectAddress fc_trig;

		/* 忽略未被克隆的触发器 */
		if (!OidIsValid(fc_pg_trigger->tgparentid))
			continue;

		/*
		 * 忽略作为外键的实现对象的内部触发器，因为这些将在外键本身被分离时被分离。
		 */
		if (OidIsValid(fc_pg_trigger->tgconstrrelid))
			continue;

		/*
		 * 这很丑陋，但很必要：移除触发器上的依赖标记，以便可以将其移除。
		 */
		deleteDependencyRecordsForClass(TriggerRelationId, fc_pg_trigger->oid,
										TriggerRelationId,
										DEPENDENCY_PARTITION_PRI);
		deleteDependencyRecordsForClass(TriggerRelationId, fc_pg_trigger->oid,
										RelationRelationId,
										DEPENDENCY_PARTITION_SEC);

		/* 记住这个触发器以便稍后移除它 */
		ObjectAddressSet(fc_trig, TriggerRelationId, fc_pg_trigger->oid);
		add_exact_object_address(&fc_trig, fc_objects);
	}

	/* 使依赖关系移除对下面的删除可见 */
	CommandCounterIncrement();
	performMultipleDeletions(fc_objects, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);

	/* 完成 */
	free_object_addresses(fc_objects);
	systable_endscan(fc_scan);
	table_close(fc_tgrel, RowExclusiveLock);
}

/*
 * 在获取索引上的锁之前，先在拥有的表上获取相同的锁。
 */
struct AttachIndexCallbackState
{
	Oid			partitionOid;
	Oid			parentTblOid;
	bool		lockedParentTbl;
};

static void fc_RangeVarCallbackForAttachIndex(const RangeVar *fc_rv, Oid fc_relOid, Oid fc_oldRelOid,
							   void *fc_arg)
{
	struct AttachIndexCallbackState *fc_state;
	Form_pg_class fc_classform;
	HeapTuple	fc_tuple;

	fc_state = (struct AttachIndexCallbackState *) fc_arg;

	if (!fc_state->lockedParentTbl)
	{
		LockRelationOid(fc_state->parentTblOid, AccessShareLock);
		fc_state->lockedParentTbl = true;
	}

	/*
	 * 如果我们之前锁定了某个堆，并且我们正在查找的名称不再指代该关系上的索引，则释放现在无用的锁。
	 * XXX 也许在我们验证该索引确实不属于同一关系之后再这样做。
	 */
	if (fc_relOid != fc_oldRelOid && OidIsValid(fc_state->partitionOid))
	{
		UnlockRelationOid(fc_state->partitionOid, AccessShareLock);
		fc_state->partitionOid = InvalidOid;
	}

	/* 没找到关系，因此无需锁定或权限检查。 */
	if (!OidIsValid(fc_relOid))
		return;

	fc_tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(fc_relOid));
	if (!HeapTupleIsValid(fc_tuple))
		return;					/* 并发删除，所以无需执行任何操作 */
	fc_classform = (Form_pg_class) GETSTRUCT(fc_tuple);
	if (fc_classform->relkind != RELKIND_PARTITIONED_INDEX &&
		fc_classform->relkind != RELKIND_INDEX)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("\"%s\" is not an index", fc_rv->relname)));
	ReleaseSysCache(fc_tuple);

	/*
	 * 由于我们只需检查堆的 tupledesc，因此对其施加访问共享锁（防止任何 DDL）就足够了。
	 */
	fc_state->partitionOid = IndexGetRelation(fc_relOid, false);
	LockRelationOid(fc_state->partitionOid, AccessShareLock);
}

/*
 * ALTER INDEX i1 ATTACH PARTITION i2
 */
static ObjectAddress fc_ATExecAttachPartitionIdx(List **fc_wqueue, Relation fc_parentIdx, RangeVar *fc_name)
{
	Relation	fc_partIdx;
	Relation	fc_partTbl;
	Relation	fc_parentTbl;
	ObjectAddress fc_address;
	Oid			fc_partIdxId;
	Oid			fc_currParent;
	struct AttachIndexCallbackState fc_state;

	/*
	 * 我们需要获取 'name' 索引上的锁以修改它，但我们也需要读取其拥有表的元组描述符——因此我们需要锁定两者。
	 * 为了避免死锁，在对索引进行锁定之前先获取表的锁。此外，我们需要检查分区的父表，因此也要锁定该表。
	 */
	fc_state.partitionOid = InvalidOid;
	fc_state.parentTblOid = fc_parentIdx->rd_index->indrelid;
	fc_state.lockedParentTbl = false;
	fc_partIdxId =
		RangeVarGetRelidExtended(fc_name, AccessExclusiveLock, 0,
								 fc_RangeVarCallbackForAttachIndex,
								 (void *) &fc_state);
	/* 不在吗？ */
	if (!OidIsValid(fc_partIdxId))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("index \"%s\" does not exist", fc_name->relname)));

	/* 没有死锁风险：RangeVarGetRelidExtended 已经获得了锁 */
	fc_partIdx = relation_open(fc_partIdxId, AccessExclusiveLock);

	/* 我们已经持有两个表的锁，因此这是安全的： */
	fc_parentTbl = relation_open(fc_parentIdx->rd_index->indrelid, AccessShareLock);
	fc_partTbl = relation_open(fc_partIdx->rd_index->indrelid, NoLock);

	ObjectAddressSet(fc_address, RelationRelationId, RelationGetRelid(fc_partIdx));

	/* 如果已经处于正确的状态，则静默不作为 */
	fc_currParent = fc_partIdx->rd_rel->relispartition ?
		get_partition_parent(fc_partIdxId, false) : InvalidOid;
	if (fc_currParent != RelationGetRelid(fc_parentIdx))
	{
		IndexInfo  *fc_childInfo;
		IndexInfo  *fc_parentInfo;
		AttrMap    *fc_attmap;
		bool		fc_found;
		int			fc_i;
		PartitionDesc fc_partDesc;
		Oid			fc_constraintOid,
					fc_cldConstrId = InvalidOid;

		/*
		 * 如果此分区已经附加了索引，拒绝此操作。
		 */
		fc_refuseDupeIndexAttach(fc_parentIdx, fc_partIdx, fc_partTbl);

		if (OidIsValid(fc_currParent))
			ereport(ERROR,
					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
					 errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
							RelationGetRelationName(fc_partIdx),
							RelationGetRelationName(fc_parentIdx)),
					 errdetail("Index \"%s\" is already attached to another index.",
							   RelationGetRelationName(fc_partIdx))));

		/* 确保它索引的是另一个索引表的分区 */
		fc_partDesc = RelationGetPartitionDesc(fc_parentTbl, true);
		fc_found = false;
		for (fc_i = 0; fc_i < fc_partDesc->nparts; fc_i++)
		{
			if (fc_partDesc->oids[fc_i] == fc_state.partitionOid)
			{
				fc_found = true;
				break;
			}
		}
		if (!fc_found)
			ereport(ERROR,
					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
					 errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
							RelationGetRelationName(fc_partIdx),
							RelationGetRelationName(fc_parentIdx)),
					 errdetail("Index \"%s\" is not an index on any partition of table \"%s\".",
							   RelationGetRelationName(fc_partIdx),
							   RelationGetRelationName(fc_parentTbl))));

		/* 确保索引是兼容的 */
		fc_childInfo = BuildIndexInfo(fc_partIdx);
		fc_parentInfo = BuildIndexInfo(fc_parentIdx);
		fc_attmap = build_attrmap_by_name(RelationGetDescr(fc_partTbl),
									   RelationGetDescr(fc_parentTbl));
		if (!CompareIndexInfo(fc_childInfo, fc_parentInfo,
							  fc_partIdx->rd_indcollation,
							  fc_parentIdx->rd_indcollation,
							  fc_partIdx->rd_opfamily,
							  fc_parentIdx->rd_opfamily,
							  fc_attmap))
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
							RelationGetRelationName(fc_partIdx),
							RelationGetRelationName(fc_parentIdx)),
					 errdetail("The index definitions do not match.")));

		/*
		 * 如果父项中有约束，确保子项中也有一个。
		 */
		fc_constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(fc_parentTbl),
														RelationGetRelid(fc_parentIdx));

		if (OidIsValid(fc_constraintOid))
		{
			fc_cldConstrId = get_relation_idx_constraint_oid(RelationGetRelid(fc_partTbl),
														  fc_partIdxId);
			if (!OidIsValid(fc_cldConstrId))
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
						 errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
								RelationGetRelationName(fc_partIdx),
								RelationGetRelationName(fc_parentIdx)),
						 errdetail("The index \"%s\" belongs to a constraint in table \"%s\" but no constraint exists for index \"%s\".",
								   RelationGetRelationName(fc_parentIdx),
								   RelationGetRelationName(fc_parentTbl),
								   RelationGetRelationName(fc_partIdx))));
		}

		/* 一切都很好——执行它 */
		IndexSetParentIndex(fc_partIdx, RelationGetRelid(fc_parentIdx));
		if (OidIsValid(fc_constraintOid))
			ConstraintSetParentConstraint(fc_cldConstrId, fc_constraintOid,
										  RelationGetRelid(fc_partTbl));

		free_attrmap(fc_attmap);

		fc_validatePartitionedIndex(fc_parentIdx, fc_parentTbl);
	}

	relation_close(fc_parentTbl, AccessShareLock);
	/* 在提交之前保留这些锁 */
	relation_close(fc_partTbl, NoLock);
	relation_close(fc_partIdx, NoLock);

	return fc_address;
}

/*
 * 验证给定分区是否已经包含附加到给定分区索引的索引。 
 * 如果是，则引发错误。
 */
static void fc_refuseDupeIndexAttach(Relation fc_parentIdx, Relation fc_partIdx, Relation fc_partitionTbl)
{
	Oid			fc_existingIdx;

	fc_existingIdx = index_get_partition(fc_partitionTbl,
									  RelationGetRelid(fc_parentIdx));
	if (OidIsValid(fc_existingIdx))
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
						RelationGetRelationName(fc_partIdx),
						RelationGetRelationName(fc_parentIdx)),
				 errdetail("Another index is already attached for partition \"%s\".",
						   RelationGetRelationName(fc_partitionTbl))));
}

/*
 * 验证附加到分区表父索引的分区索引集是否完整。 
 * 如果是，则标记父索引为有效。
 *
 * 每次附加分区索引时都应调用此函数。
 */
static void fc_validatePartitionedIndex(Relation fc_partedIdx, Relation fc_partedTbl)
{
	Relation	fc_inheritsRel;
	SysScanDesc fc_scan;
	ScanKeyData fc_key;
	int			fc_tuples = 0;
	HeapTuple	fc_inhTup;
	bool		fc_updated = false;

	Assert(fc_partedIdx->rd_rel->relkind == RELKIND_PARTITIONED_INDEX);

	/*
	 * 在 pg_inherits 中扫描此父索引。  计算我们找到的每个有效索引 
	 * （验证每个 pg_index 条目），如果达到我们预期的总数，
	 * 我们可以将此父索引标记为有效。
	 */
	fc_inheritsRel = table_open(InheritsRelationId, AccessShareLock);
	ScanKeyInit(&fc_key, Anum_pg_inherits_inhparent,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(RelationGetRelid(fc_partedIdx)));
	fc_scan = systable_beginscan(fc_inheritsRel, InheritsParentIndexId, true,
							  NULL, 1, &fc_key);
	while ((fc_inhTup = systable_getnext(fc_scan)) != NULL)
	{
		Form_pg_inherits fc_inhForm = (Form_pg_inherits) GETSTRUCT(fc_inhTup);
		HeapTuple	fc_indTup;
		Form_pg_index fc_indexForm;

		fc_indTup = SearchSysCache1(INDEXRELID,
								 ObjectIdGetDatum(fc_inhForm->inhrelid));
		if (!HeapTupleIsValid(fc_indTup))
			elog(ERROR, "cache lookup failed for index %u", fc_inhForm->inhrelid);
		fc_indexForm = (Form_pg_index) GETSTRUCT(fc_indTup);
		if (fc_indexForm->indisvalid)
			fc_tuples += 1;
		ReleaseSysCache(fc_indTup);
	}

	/* 完成 pg_inherits */
	systable_endscan(fc_scan);
	table_close(fc_inheritsRel, AccessShareLock);

	/*
	 * 如果我们找到的继承索引数量与分区表的分区数量相同，
	 * 那我们就可以了；更新 pg_index 以设置 indisvalid。
	 */
	if (fc_tuples == RelationGetPartitionDesc(fc_partedTbl, true)->nparts)
	{
		Relation	fc_idxRel;
		HeapTuple	fc_indTup;
		Form_pg_index fc_indexForm;

		fc_idxRel = table_open(IndexRelationId, RowExclusiveLock);
		fc_indTup = SearchSysCacheCopy1(INDEXRELID,
									 ObjectIdGetDatum(RelationGetRelid(fc_partedIdx)));
		if (!HeapTupleIsValid(fc_indTup))
			elog(ERROR, "cache lookup failed for index %u",
				 RelationGetRelid(fc_partedIdx));
		fc_indexForm = (Form_pg_index) GETSTRUCT(fc_indTup);

		fc_indexForm->indisvalid = true;
		fc_updated = true;

		CatalogTupleUpdate(fc_idxRel, &fc_indTup->t_self, fc_indTup);

		table_close(fc_idxRel, RowExclusiveLock);
		heap_freetuple(fc_indTup);
	}

	/*
	 * 如果此索引反过来又是更大索引的一个分区，
	 * 验证它可能导致父项也变为有效。 尝试这样做。
	 */
	if (fc_updated && fc_partedIdx->rd_rel->relispartition)
	{
		Oid			fc_parentIdxId,
					fc_parentTblId;
		Relation	fc_parentIdx,
					fc_parentTbl;

		/* 确保我们看到刚刚进行的验证 */
		CommandCounterIncrement();

		fc_parentIdxId = get_partition_parent(RelationGetRelid(fc_partedIdx), false);
		fc_parentTblId = get_partition_parent(RelationGetRelid(fc_partedTbl), false);
		fc_parentIdx = relation_open(fc_parentIdxId, AccessExclusiveLock);
		fc_parentTbl = relation_open(fc_parentTblId, AccessExclusiveLock);
		Assert(!fc_parentIdx->rd_index->indisvalid);

		fc_validatePartitionedIndex(fc_parentIdx, fc_parentTbl);

		relation_close(fc_parentIdx, AccessExclusiveLock);
		relation_close(fc_parentTbl, AccessExclusiveLock);
	}
}

/*
 * 返回引用给定关系的约束的 OID 列表，
 * 这些约束被标记为具有父约束。
 */
static List * fc_GetParentedForeignKeyRefs(Relation fc_partition)
{
	Relation	fc_pg_constraint;
	HeapTuple	fc_tuple;
	SysScanDesc fc_scan;
	ScanKeyData fc_key[2];
	List	   *fc_constraints = NIL;

	/*
	 * 如果没有索引，或者没有列可被外键引用，我们可以避免扫描。
	 */
	if (RelationGetIndexList(fc_partition) == NIL ||
		bms_is_empty(RelationGetIndexAttrBitmap(fc_partition,
												INDEX_ATTR_BITMAP_KEY)))
		return NIL;

	/* 搜索引用此表的约束 */
	fc_pg_constraint = table_open(ConstraintRelationId, AccessShareLock);
	ScanKeyInit(&fc_key[0],
				Anum_pg_constraint_confrelid, BTEqualStrategyNumber,
				F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(fc_partition)));
	ScanKeyInit(&fc_key[1],
				Anum_pg_constraint_contype, BTEqualStrategyNumber,
				F_CHAREQ, CharGetDatum(CONSTRAINT_FOREIGN));

	/* XXX 这是一个顺序扫描，因为我们没有可用的索引 */
	fc_scan = systable_beginscan(fc_pg_constraint, InvalidOid, true, NULL, 2, fc_key);
	while ((fc_tuple = systable_getnext(fc_scan)) != NULL)
	{
		Form_pg_constraint fc_constrForm = (Form_pg_constraint) GETSTRUCT(fc_tuple);

		/*
		 * 我们只需要处理作为较大约束一部分的约束。
		 */
		if (!OidIsValid(fc_constrForm->conparentid))
			continue;

		fc_constraints = lappend_oid(fc_constraints, fc_constrForm->oid);
	}

	systable_endscan(fc_scan);
	table_close(fc_pg_constraint, AccessShareLock);

	return fc_constraints;
}

/*
 * 在 DETACH PARTITION 期间，验证指向分区表的任何外键不会变得无效。
 * 如果存在任何引用值，将引发错误。
 */
static void fc_ATDetachCheckNoForeignKeyRefs(Relation fc_partition)
{
	List	   *fc_constraints;
	ListCell   *fc_cell;

	fc_constraints = fc_GetParentedForeignKeyRefs(fc_partition);

	foreach(fc_cell, fc_constraints)
	{
		Oid			fc_constrOid = lfirst_oid(fc_cell);
		HeapTuple	fc_tuple;
		Form_pg_constraint fc_constrForm;
		Relation	fc_rel;
		Trigger		fc_trig;

		fc_tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fc_constrOid));
		if (!HeapTupleIsValid(fc_tuple))
			elog(ERROR, "cache lookup failed for constraint %u", fc_constrOid);
		fc_constrForm = (Form_pg_constraint) GETSTRUCT(fc_tuple);

		Assert(OidIsValid(fc_constrForm->conparentid));
		Assert(fc_constrForm->confrelid == RelationGetRelid(fc_partition));

		/* 防止在提交之前对引用表进行数据更改 */
		fc_rel = table_open(fc_constrForm->conrelid, ShareLock);

		MemSet(&fc_trig, 0, sizeof(fc_trig));
		fc_trig.tgoid = InvalidOid;
		fc_trig.tgname = NameStr(fc_constrForm->conname);
		fc_trig.tgenabled = TRIGGER_FIRES_ON_ORIGIN;
		fc_trig.tgisinternal = true;
		fc_trig.tgconstrrelid = RelationGetRelid(fc_partition);
		fc_trig.tgconstrindid = fc_constrForm->conindid;
		fc_trig.tgconstraint = fc_constrForm->oid;
		fc_trig.tgdeferrable = false;
		fc_trig.tginitdeferred = false;
		/* 我们不需要填写剩余字段 */

		RI_PartitionRemove_Check(&fc_trig, fc_rel, fc_partition);

		ReleaseSysCache(fc_tuple);

		table_close(fc_rel, NoLock);
	}
}

/*
 * 将列压缩规范解析为压缩方法。
 */
static char fc_GetAttributeCompression(Oid fc_atttypid, char *fc_compression)
{
	char		fc_cmethod;

	if (fc_compression == NULL || strcmp(fc_compression, "default") == 0)
		return InvalidCompressionMethod;

	/*
	 * 要指定非默认方法，列数据类型必须可压缩。
	 * 注意，这并未说明列的 attstorage 设置
	 * 是否允许压缩；我们故意允许 attstorage 和
	 * attcompression 独立。 但是，对于不可压缩类型，
	 * attstorage 不能设置为允许压缩的值。
	 *
	 * 我们实际上并不需要强制执行这一点，因为如果
	 * attcompression 不是默认值，也不会发生任何坏事；它将永远不会被使用。
	 * 但是，抱怨毫无用处的尝试设置该属性似乎更人性化。
	 */
	if (!TypeIsToastable(fc_atttypid))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("column data type %s does not support compression",
						format_type_be(fc_atttypid))));

	fc_cmethod = CompressionNameToMethod(fc_compression);
	if (!CompressionMethodIsValid(fc_cmethod))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("invalid compression method \"%s\"", fc_compression)));

	return fc_cmethod;
}
